Skip to content

Latest commit

 

History

History
188 lines (142 loc) · 6.58 KB

File metadata and controls

188 lines (142 loc) · 6.58 KB
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

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-ui

Core patterns

Compound component structure

Every 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>

Imports

Import named components from "bits-ui":

import { Dialog, Select, Accordion, Popover, Tooltip } from "bits-ui";

State management

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.

Accessing DOM elements (refs)

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.

Styling

Bits UI ships unstyled. Three approaches:

  1. class prop — apply Tailwind, UnoCSS, or any utility classes directly
  2. 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.
  3. CSS variables — for dynamic sizing: --bits-accordion-content-height, --bits-select-anchor-width, etc.

The child snippet — custom elements

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 child snippet is used

Floating content (Popover, Tooltip, Select, etc.)

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

Svelte transitions without child

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>

Available components

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

Utilities

  • mergeProps(...props) — merge multiple prop objects, concatenating event handlers and classes
  • <Portal> — render content outside the current DOM hierarchy
  • isUsingKeyboard() — reactive boolean for keyboard vs. pointer detection
  • <BitsConfig> — global configuration provider
  • useId() — generate stable unique IDs

Fetching component documentation

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.txt
  • https://bits-ui.com/docs/components/select/llms.txt
  • https://bits-ui.com/docs/components/combobox/llms.txt

General topic docs:

  • https://bits-ui.com/docs/state-management/llms.txt
  • https://bits-ui.com/docs/child-snippet/llms.txt
  • https://bits-ui.com/docs/styling/llms.txt
  • https://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.

Common pitfalls

  • Missing {...props} spread inside child snippet — breaks accessibility and event handling
  • Styling the wrapper in floating components — breaks positioning
  • Setting id on 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)