| name | bits-ui |
|---|---|
| description | Guide for building UI components with Bits UI, the headless Svelte 5 component library. Use this skill whenever the user is working with Bits UI components, importing from "bits-ui", building accessible UI primitives in Svelte, or asking about dialogs, selects, comboboxes, accordions, popovers, tooltips, dropdowns, date pickers, or any other headless component in a Svelte project. Trigger even if the user just mentions "bits-ui" or asks to "add a dialog/select/dropdown" in a Svelte project. |
Bits UI is a headless component library for Svelte 5. Components ship unstyled — you provide all styling via classes, data attributes, or CSS variables.
Install:
npm install bits-uiEvery Bits UI component uses a dot-notation compound pattern:
<script lang="ts">
import { Accordion } from "bits-ui";
</script>
<Accordion.Root type="single">
<Accordion.Item value="item-1">
<Accordion.Header>
<Accordion.Trigger>Title</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content>Content here</Accordion.Content>
</Accordion.Item>
</Accordion.Root>Import named components from "bits-ui":
import { Dialog, Select, Accordion, Popover, Tooltip } from "bits-ui";Two-way binding (simple):
let open = $state(false);
let value = $state("");
<Dialog.Root bind:open>
<Select.Root bind:value>Function binding (full control — lets you intercept or gate updates):
<Select.Root bind:value={
() => currentValue,
(v) => { if (isValid(v)) currentValue = v; }
}>Each component's API docs indicate which props are bindable.
let triggerEl = $state<HTMLButtonElement | null>(null);
<Dialog.Trigger bind:ref={triggerEl}>Open</Dialog.Trigger>Important: Never set id directly on a child element inside {#snippet child(...)} — pass id to the parent component instead so it's included in the merged props.
Bits UI ships unstyled. Three approaches:
classprop — apply Tailwind, UnoCSS, or any utility classes directly- Data attributes — each component exposes data attributes for CSS targeting:
- State:
[data-state="open"],[data-disabled],[data-highlighted] - Identity:
[data-accordion-trigger],[data-dialog-content], etc.
- State:
- CSS variables — for dynamic sizing:
--bits-accordion-content-height,--bits-select-anchor-width, etc.
Use {#snippet child({ props })} to replace a component's default element with your own, enabling scoped styles, Svelte actions, or custom components:
<Accordion.Trigger>
{#snippet child({ props })}
<button {...props} class="my-trigger">Toggle</button>
{/snippet}
</Accordion.Trigger>Critical rules:
- Always spread
{...props}— it contains ARIA attributes, event handlers, and internal state - Pass custom
id,onclick, etc. to the parent component, not inside the snippet - Outside children are ignored when
childsnippet is used
Floating components require a two-level structure inside child:
<Popover.Content>
{#snippet child({ wrapperProps, props, open })}
{#if open}
<div {...wrapperProps}> <!-- outer: handles positioning, leave unstyled -->
<div {...props} transition:fade> <!-- inner: add styles and transitions here -->
Content
</div>
</div>
{/if}
{/snippet}
</Popover.Content>Never style the wrapper element (wrapperProps). Styling goes on the inner element only.
Components requiring two-level structure:
Combobox.Content, DatePicker.Content, DateRangePicker.Content, DropdownMenu.Content, LinkPreview.Content, Menubar.Content, Popover.Content, Select.Content, Tooltip.Content
Use forceMount + child to animate open/close with Svelte transitions on non-floating components:
<Dialog.Content forceMount>
{#snippet child({ props, open })}
{#if open}
<div {...props} transition:scale={{ start: 0.95 }}>
Dialog content
</div>
{/if}
{/snippet}
</Dialog.Content>| Category | Components |
|---|---|
| Overlay/Dialog | Dialog, Alert Dialog, Popover, Tooltip, Link Preview |
| Menus | Dropdown Menu, Context Menu, Menubar, Navigation Menu |
| Form inputs | Button, Checkbox, Radio Group, Switch, Toggle, Toggle Group, Slider, Pin Input, Label |
| Select/Search | Select, Combobox, Command |
| Date/Time | Calendar, Range Calendar, Date Field, Date Range Field, Date Picker, Date Range Picker, Time Field, Time Range Field |
| Layout/Display | Accordion, Collapsible, Tabs, Scroll Area, Separator, Aspect Ratio, Avatar |
| Feedback | Progress, Meter, Rating Group, Pagination |
| Navigation | Toolbar |
mergeProps(...props)— merge multiple prop objects, concatenating event handlers and classes<Portal>— render content outside the current DOM hierarchyisUsingKeyboard()— reactive boolean for keyboard vs. pointer detection<BitsConfig>— global configuration provideruseId()— generate stable unique IDs
When you need detailed props, full API tables, or advanced examples for a specific component, fetch its docs:
https://bits-ui.com/docs/components/{component-name}/llms.txt
For example:
https://bits-ui.com/docs/components/dialog/llms.txthttps://bits-ui.com/docs/components/select/llms.txthttps://bits-ui.com/docs/components/combobox/llms.txt
General topic docs:
https://bits-ui.com/docs/state-management/llms.txthttps://bits-ui.com/docs/child-snippet/llms.txthttps://bits-ui.com/docs/styling/llms.txthttps://bits-ui.com/docs/transitions/llms.txt
Always fetch the component's llms.txt before writing complex components or when the user asks about specific props. The docs include full prop tables with types, defaults, and descriptions.
- Missing
{...props}spread insidechildsnippet — breaks accessibility and event handling - Styling the wrapper in floating components — breaks positioning
- Setting
idon the child element instead of the parent component — breaks ref bindings - Forgetting two-level structure for floating content — breaks Floating UI positioning
- Using Svelte 4 patterns — Bits UI requires Svelte 5 (runes, snippets)