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.
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.
- Zero JavaScript Overhead - Web components load only the JavaScript they need
- Server-Side Rendering - Astro renders the initial HTML on the server
- Progressive Enhancement - Interactivity loads only when needed
- No Build-Time Complexity - No need for framework-specific helpers
- Native Browser APIs - Use
document.createElementdirectly
Add the design system to your Astro project:
npm install dap-design-system
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';
---
In a <script> tag, import the web components:
<script>
import 'dap-design-system/components';
import 'dap-design-system/icons';
</script>
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>
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).
.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
Instead of .astro components, create reusable cell renderer functions that follow a component-like pattern:
For simple text or numbers, just return the value directly:
{
id: 'name',
header: 'Name',
accessorKey: 'name' // Returns the value as-is
}
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)
})
}
];
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
})
}
];
Important: Web components use both properties and attributes, but they behave differently:
Use setAttribute for simple string values:
datatable.setAttribute('loading-type', 'spinner');
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!
Make sure you've imported the components in a <script> tag:
<script>
import 'dap-design-system/components';
import 'dap-design-system/icons';
</script>
Import CSS in the frontmatter (server-side):
---
import 'dap-design-system/styles/components.native.css';
import 'dap-design-system/styles/light.theme.css';
---
Use property assignment (not setAttribute) for complex values:
// ✅ Correct
datatable.data = newData;
// ❌ Won't work
datatable.setAttribute('data', JSON.stringify(newData));
Cast the element if TypeScript complains:
const datatable = document.createElement('dap-ds-datatable') as any;
datatable.data = users; // No error