Astro Integration for Datatable The Astro Advantage

Astro is a server-first web framework that delivers zero JavaScript by default. When using the datatable component with Astro, you have the unique advantage of leveraging native web components without requiring any framework-specific helpers.

Why Web Components are Perfect for Astro

Web components are already framework-agnostic and work natively in all browsers. Unlike Vue, React, or other frameworks that require special rendering logic, web components can be used directly in Astro without any wrappers or helpers.

Key Benefits:
  1. Zero JavaScript Overhead - Web components load only the JavaScript they need
  2. Server-Side Rendering - Astro renders the initial HTML on the server
  3. Progressive Enhancement - Interactivity loads only when needed
  4. No Build-Time Complexity - No need for framework-specific helpers
  5. Native Browser APIs - Use document.createElement directly
Installation

Add the design system to your Astro project:

npm install dap-design-system
Basic Usage Import Styles (Server-Side)

In your Astro component's frontmatter, import the required CSS:

---
import 'dap-design-system/styles/components.native.css';
import 'dap-design-system/styles/light.theme.css';
---
Import Components (Client-Side)

In a <script> tag, import the web components:

<script>
  import 'dap-design-system/components';
  import 'dap-design-system/icons';
</script>
Complete Example with Remote Data

This example demonstrates the recommended pattern for Astro: using native DOM APIs to create cell content.

---
import Layout from '../layouts/Layout.astro';
import 'dap-design-system/styles/components.native.css';
import 'dap-design-system/styles/light.theme.css';
---

<Layout>
  <div id="datatable-container"></div>
</Layout>

<script>
  import 'dap-design-system/components';
  import 'dap-design-system/icons';

  // Mock data generator
  function generateMockUsers(page: number, pageSize: number) {
    const total = 100;
    const start = (page - 1) * pageSize;
    const end = Math.min(start + pageSize, total);
    const statuses = ['active', 'pending', 'inactive'];
    const data = [];

    for (let i = start; i < end; i++) {
      data.push({
        id: i + 1,
        name: `User ${i + 1}`,
        email: `user${i + 1}@example.com`,
        status: statuses[i % 3]
      });
    }

    return { data, total };
  }

  // State
  let currentPage = 0;
  const pageSize = 10;

  // Column definitions using native DOM API
  const columns = [
    {
      id: 'id',
      header: 'ID',
      accessorKey: 'id',
      size: 60
    },
    {
      id: 'name',
      header: 'Name',
      accessorKey: 'name'
    },
    {
      id: 'email',
      header: 'Email',
      accessorKey: 'email'
    },
    {
      id: 'status',
      header: 'Status',
      cell: (info: any) => {
        const row = info.row.original;
        const variant = row.status === 'active' ? 'success'
          : row.status === 'pending' ? 'warning'
          : 'danger';

        // ✅ Recommended for Astro - use native DOM
        const badge = document.createElement('dap-ds-badge');
        badge.setAttribute('variant', variant);
        badge.textContent = row.status;
        return badge;
      }
    },
    {
      id: 'actions',
      header: 'Actions',
      cell: (info: any) => {
        const row = info.row.original;
        const container = document.createElement('div');
        container.style.display = 'flex';
        container.style.gap = '0.5rem';

        const editBtn = document.createElement('dap-ds-button');
        editBtn.setAttribute('size', 'xs');
        editBtn.setAttribute('variant', 'primary');
        editBtn.textContent = 'Edit';
        editBtn.addEventListener('click', () => {
          alert(`Editing: ${row.name}`);
        });

        const deleteBtn = document.createElement('dap-ds-button');
        deleteBtn.setAttribute('size', 'xs');
        deleteBtn.setAttribute('variant', 'danger');
        deleteBtn.textContent = 'Delete';
        deleteBtn.addEventListener('click', () => {
          if (confirm(`Delete ${row.name}?`)) {
            fetchData();
          }
        });

        container.appendChild(editBtn);
        container.appendChild(deleteBtn);
        return container;
      }
    }
  ];

  // Fetch and update data
  function fetchData() {
    updateTable([], true);

    setTimeout(() => {
      const result = generateMockUsers(currentPage + 1, pageSize);
      updateTable(result.data, false, result.total);
    }, 500);
  }

  function updateTable(data: any[], isLoading: boolean, total?: number) {
    const container = document.getElementById('datatable-container');
    if (!container) return;

    const existing = container.querySelector('dap-ds-datatable');
    if (existing) container.removeChild(existing);

    const datatable = document.createElement('dap-ds-datatable');

    // Set properties (not attributes for complex values)
    (datatable as any).data = data;
    (datatable as any).columns = columns;
    (datatable as any).rowCount = total || 0;
    (datatable as any).pager = true;
    (datatable as any).pagination = { pageIndex: currentPage, pageSize };
    (datatable as any).manualPagination = true;
    (datatable as any).loading = isLoading;
    datatable.setAttribute('loading-type', 'spinner');

    datatable.addEventListener('dds-pagination-change', (event: any) => {
      currentPage = event.detail.pagination.pageIndex;
      fetchData();
    });

    container.appendChild(datatable);
  }

  fetchData();
</script>
Understanding Astro Components vs Client-Side Rendering

Important: Astro components (.astro files) are server-side only and cannot be used for dynamic client-side rendering. The datatable needs to create cells dynamically in the browser when data changes (pagination, sorting, filtering).

Why Not Use .astro Components for Cells?
  • ❌ Astro components are rendered at build time or request time on the server
  • ❌ They cannot be instantiated or rendered in the browser
  • ❌ Datatable cells need to be created dynamically based on changing data
Recommended Approach: Component-Like Cell Renderers

Instead of .astro components, create reusable cell renderer functions that follow a component-like pattern:

Cell Rendering Patterns Pattern 1: Simple Values (Static)

For simple text or numbers, just return the value directly:

{
  id: 'name',
  header: 'Name',
  accessorKey: 'name' // Returns the value as-is
}
Pattern 2: Reusable Cell Renderer Functions

Create component-like functions in separate files for reusability:

File: src/components/cells/StatusBadgeCell.ts

export interface StatusBadgeCellProps {
  status: string;
  onClick?: (status: string) => void;
}

export function createStatusBadgeCell(props: StatusBadgeCellProps): HTMLElement {
  const { status, onClick } = props;
  const variant = status === 'active' ? 'success'
    : status === 'pending' ? 'warning'
    : 'danger';

  const badge = document.createElement('dap-ds-badge');
  badge.setAttribute('variant', variant);
  badge.textContent = status;

  if (onClick) {
    badge.style.cursor = 'pointer';
    badge.addEventListener('click', () => onClick(status));
  }

  return badge;
}

Usage in datatable:

import { createStatusBadgeCell } from '../components/cells';

const columns = [
  {
    id: 'status',
    header: 'Status',
    cell: (info) => createStatusBadgeCell({
      status: info.row.original.status,
      onClick: (status) => console.log('Clicked:', status)
    })
  }
];
Pattern 3: Complex Action Cells

File: src/components/cells/ActionButtonsCell.ts

export interface ActionButtonsCellProps {
  rowData: any;
  onEdit?: (rowData: any) => void;
  onDelete?: (rowData: any) => void;
}

export function createActionButtonsCell(props: ActionButtonsCellProps): HTMLElement {
  const { rowData, onEdit, onDelete } = props;
  const container = document.createElement('div');
  container.style.display = 'flex';
  container.style.gap = '0.5rem';

  if (onEdit) {
    const editBtn = document.createElement('dap-ds-button');
    editBtn.setAttribute('size', 'xs');
    editBtn.setAttribute('variant', 'primary');
    editBtn.textContent = 'Edit';
    editBtn.addEventListener('click', () => onEdit(rowData));
    container.appendChild(editBtn);
  }

  if (onDelete) {
    const deleteBtn = document.createElement('dap-ds-button');
    deleteBtn.setAttribute('size', 'xs');
    deleteBtn.setAttribute('variant', 'danger');
    deleteBtn.textContent = 'Delete';
    deleteBtn.addEventListener('click', () => onDelete(rowData));
    container.appendChild(deleteBtn);
  }

  return container;
}

Usage:

import { createActionButtonsCell } from '../components/cells';

function handleEdit(rowData) {
  alert(`Editing: ${rowData.name}`);
}

function handleDelete(rowData) {
  if (confirm(`Delete ${rowData.name}?`)) {
    // Delete logic
  }
}

const columns = [
  {
    id: 'actions',
    header: 'Actions',
    cell: (info) => createActionButtonsCell({
      rowData: info.row.original,
      onEdit: handleEdit,
      onDelete: handleDelete
    })
  }
];
Properties vs Attributes

Important: Web components use both properties and attributes, but they behave differently:

Attributes (String Values)

Use setAttribute for simple string values:

datatable.setAttribute('loading-type', 'spinner');
Properties (Complex Values)

Use direct property assignment for objects, arrays, and booleans:

// ✅ Correct - use properties
datatable.data = users;
datatable.columns = columns;
datatable.pagination = { pageIndex: 0, pageSize: 10 };
datatable.manualPagination = true;

// ❌ Incorrect - don't use setAttribute for objects
datatable.setAttribute('data', JSON.stringify(users)); // Won't work!
Troubleshooting Components Not Rendering

Make sure you've imported the components in a <script> tag:

<script>
  import 'dap-design-system/components';
  import 'dap-design-system/icons';
</script>
Styles Not Loading

Import CSS in the frontmatter (server-side):

---
import 'dap-design-system/styles/components.native.css';
import 'dap-design-system/styles/light.theme.css';
---
Data Not Updating

Use property assignment (not setAttribute) for complex values:

// ✅ Correct
datatable.data = newData;

// ❌ Won't work
datatable.setAttribute('data', JSON.stringify(newData));
TypeScript Errors

Cast the element if TypeScript complains:

const datatable = document.createElement('dap-ds-datatable') as any;
datatable.data = users; // No error