Astro is a server-first web framework that delivers zero JavaScript by default. Web components work perfectly with Astro because they're already framework-agnostic and use native browser APIs.
✅ 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 Framework Wrapper - Use native DOM APIs directly
Install dap-design-system inside your Astro project folder:
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';
</script>
---
import Layout from '../layouts/Layout.astro';
import 'dap-design-system/styles/components.native.css';
import 'dap-design-system/styles/light.theme.css';
---
<Layout>
<main>
<h1>My Astro App</h1>
<!-- Use web components directly -->
<dap-ds-input
label="Email"
type="email"
required
helper-text="Enter your email address"
></dap-ds-input>
<dap-ds-button variant="primary">
Submit
</dap-ds-button>
</main>
</Layout>
<script>
import 'dap-design-system/components';
// Add event listeners for interactivity
const input = document.querySelector('dap-ds-input');
const button = document.querySelector('dap-ds-button');
input?.addEventListener('dds-change', (event) => {
console.log('Input changed:', event.detail.value);
});
button?.addEventListener('click', () => {
console.log('Button clicked');
});
</script>
Important: Web components use both properties and attributes, but they behave differently:
Use HTML attributes for simple string values:
<dap-ds-input
label="Username"
type="text"
placeholder="Enter username"
></dap-ds-input>
Use JavaScript properties for objects, arrays, and booleans:
<div id="table-container"></div>
<script>
import 'dap-design-system/components';
const datatable = document.createElement('dap-ds-datatable');
// ✅ Use properties for complex values
datatable.data = [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' }
];
datatable.columns = [
{ id: 'id', header: 'ID', accessorKey: 'id' },
{ id: 'name', header: 'Name', accessorKey: 'name' },
{ id: 'email', header: 'Email', accessorKey: 'email' }
];
datatable.pagination = { pageIndex: 0, pageSize: 10 };
// ✅ Use attributes for strings
datatable.setAttribute('loading-type', 'spinner');
document.getElementById('table-container').appendChild(datatable);
</script>
For better integration with Vite's optimization, you can configure astro.config.mjs:
import { defineConfig } from 'astro/config';
export default defineConfig({
vite: {
optimizeDeps: {
include: ['dap-design-system/components'],
},
},
});
Create web components dynamically using native DOM APIs:
<div id="app"></div>
<script>
import 'dap-design-system/components';
const container = document.getElementById('app');
// Create badge
const badge = document.createElement('dap-ds-badge');
badge.setAttribute('variant', 'success');
badge.textContent = 'Active';
container.appendChild(badge);
// Create button with event listener
const button = document.createElement('dap-ds-button');
button.setAttribute('variant', 'primary');
button.textContent = 'Click me';
button.addEventListener('click', () => {
alert('Button clicked!');
});
container.appendChild(button);
</script>
Handle form submissions and validation:
<form id="user-form">
<dap-ds-input
id="name-input"
name="name"
label="Name"
required
helper-text="Enter your full name"
></dap-ds-input>
<dap-ds-input
id="email-input"
name="email"
type="email"
label="Email"
required
helper-text="Enter your email address"
></dap-ds-input>
<dap-ds-button type="submit" variant="primary">
Submit
</dap-ds-button>
</form>
<script>
import 'dap-design-system/components';
const form = document.getElementById('user-form');
form?.addEventListener('submit', (event) => {
event.preventDefault();
const formData = new FormData(form);
const data = Object.fromEntries(formData);
console.log('Form submitted:', data);
// Send to API
fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
});
// Listen to input changes
const inputs = form.querySelectorAll('dap-ds-input');
inputs.forEach(input => {
input.addEventListener('dds-change', (event) => {
console.log(`${event.target.name} changed:`, event.detail.value);
});
});
</script>
While native web components are recommended for Astro, you can use framework islands for complex interactive components:
---
import { createReactCell } from 'dap-design-system/helpers/react';
import ComplexComponent from './components/ComplexComponent.tsx';
---
<script>
const container = document.getElementById('react-island');
const reactCell = createReactCell(
<ComplexComponent client:load data={myData} />
);
reactCell.render(container);
</script>
---
import { createVueCell } from 'dap-design-system/helpers/vue';
import MyComponent from './components/MyComponent.vue';
---
<script>
const container = document.getElementById('vue-island');
const vueCell = createVueCell(MyComponent, {
prop1: 'value1',
prop2: 'value2'
});
vueCell.render(container);
</script>
Import components in the <script> tag instead of the frontmatter for client-side loading:
<!-- ✅ Good - loads on client -->
<script>
import 'dap-design-system/components';
</script>
<!-- ❌ Avoid - loads on server (unnecessary) -->
---
import 'dap-design-system/components';
---
Let Astro render the initial HTML structure on the server:
---
// Server-rendered content
const users = await fetch('/api/users').then(r => r.json());
---
<div>
{users.map(user => (
<div class="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
))}
</div>
Use Astro's client: directives for components that don't need immediate interactivity:
<script>
// Only load when component becomes visible
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
import('dap-design-system/components');
observer.disconnect();
}
});
});
const target = document.getElementById('lazy-component');
if (target) observer.observe(target);
</script>
For better TypeScript support, cast elements when needed:
const datatable = document.createElement('dap-ds-datatable') as any;
datatable.data = users; // No TypeScript error
datatable.columns = columns;
Or use type definitions if available:
import type { DapDSDataTable } from 'dap-design-system';
const datatable = document.createElement('dap-ds-datatable') as DapDSDataTable;
Make sure you've imported the components in a <script> tag:
<script>
import 'dap-design-system/components';
</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 direct property assignment (not setAttribute) for complex values:
// ✅ Correct
datatable.data = users;
datatable.columns = columns;
// ❌ Won't work
datatable.setAttribute('data', JSON.stringify(users));
Check out the complete Astro example project in the repository: