March 30, 2026
2 min
MSMojtaba SeyediContent Writer
Astro is a great fit for teams that want to ship fast, lean sites. Server-first, zero JS by default, full control over what goes to the browser. It's a compelling stack — until you need a modal.
Not because modals are hard to build. Because building them correctly is a different problem. Focus trapping, keyboard navigation, ARIA roles, escape-to-close, backdrop handling — these are the details that separate a component that looks right from one that actually works for every user.
This is the moment most Astro projects quietly add React.
We didn't want to do that. So we built something instead.
When we started building bejamas/ui — an open-source UI component library for Astro — most components were straightforward. Cards, buttons, badges, typography. Pure HTML and Tailwind CSS. No JavaScript needed.
But some components can't be CSS-only. Dialogs, accordions, tabs — they need behavior. And that behavior needs to be accessible, or it's not really done.
The options on the table weren't great:
None of these fit well with what Astro projects actually look like.
@data-slot is a collection of headless, framework-agnostic JavaScript packages — one per UI pattern. Each package is tiny, unstyled, and focused on a single job: take a plain HTML element and wire up the behavior it needs.
At the time of writing, there are 15 packages in total, covering the interactive patterns you'll actually reach for.

The largest package is 10.3 KB. Most are under 4 KB. You only install what you need.
Install only the packages you need:
bun add @data-slot/tabs
bun add @data-slot/dialogMark your HTML with data-slot attributes:
<div data-slot="tabs" data-default-value="one">
<div data-slot="tabs-list">
<button data-slot="tabs-trigger" data-value="one">Tab One</button>
<button data-slot="tabs-trigger" data-value="two">Tab Two</button>
</div>
<div data-slot="tabs-content" data-value="one">Content One</div>
<div data-slot="tabs-content" data-value="two">Content Two</div>
</div>Then call create() to bind the behavior:
import { create } from "@data-slot/tabs";
// Auto-discover and bind all [data-slot="tabs"] elements
const controllers = create();
// Or target a specific element
import { createTabs } from "@data-slot/tabs";
const tabs = createTabs(element);That's it. Keyboard navigation, ARIA attributes, focus management — all handled. You keep full control over markup and styling.
Astro components are server-rendered by default. @data-slot fits cleanly into that model — components render as real HTML first, JavaScript enhances them after. Progressive enhancement with no special configuration.
Because each package is small and independent, you only ship behavior for what you actually use. A project with no dialogs sends no dialog JavaScript to the browser.
And because behavior lives in a versioned package rather than inlined into component files, accessibility fixes and improvements propagate through a regular dependency update — not a manual find-and-replace across your codebase.
Go to data-slot.com and give it a try. Also, If you want a full Astro component library built on top of @data-slot, check out bejamas/ui.