Astro

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.

Why Astro + Web Components?

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

Installation

Install dap-design-system inside your Astro project folder:

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';
</script>
Using Components
---
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>
Properties vs Attributes

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

Attributes (String Values)

Use HTML attributes for simple string values:

<dap-ds-input
  label="Username"
  type="text"
  placeholder="Enter username"
></dap-ds-input>
Properties (Complex Values)

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>
Configuration (Optional)

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'],
    },
  },
});
Dynamic Component Creation

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>
Form Handling

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>
Using Framework Islands (Advanced)

While native web components are recommended for Astro, you can use framework islands for complex interactive components:

With React
---
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>
With Vue
---
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>
Performance Best Practices 1. Import Components Only When Needed

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';
---
2. Use Server-Side Rendering

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>
3. Lazy Load Components

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>
TypeScript Support

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;
Troubleshooting Components Not Rendering

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

<script>
  import 'dap-design-system/components';
</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';
---
Properties Not Working

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));
Examples

Check out the complete Astro example project in the repository:

Learn More