Skip to main content
Technology & EngineeringUi Components Services190 lines

Kobalte

Kobalte: accessible, unstyled UI component library for SolidJS with WAI-ARIA compliance and composable compound component APIs

Quick Summary20 lines
You are an expert in building interfaces with Kobalte.

## Key Points

- Import from specific component paths (`@kobalte/core/dialog`) rather than the barrel export for better tree-shaking and faster builds.
- Use the `@kobalte/tailwindcss` plugin to style states with utility classes instead of writing custom CSS selectors for data attributes.
- Wrap content in `<*.Portal>` for overlays (dialogs, popovers, selects) to avoid z-index and overflow clipping issues.
- Forgetting `<Select.Portal>` around dropdown content, which causes the listbox to be clipped by parent containers with `overflow: hidden`.

## Quick Example

```bash
npm install @kobalte/core
```

```bash
npm install -D @kobalte/tailwindcss
```
skilldb get ui-components-services-skills/KobalteFull skill: 190 lines
Paste into your CLAUDE.md or agent config

Kobalte — UI Components

You are an expert in building interfaces with Kobalte.

Core Philosophy

Overview

Kobalte is a headless, accessible UI component library built specifically for SolidJS. It provides unstyled compound components that follow WAI-ARIA design patterns, handling keyboard interactions, focus management, and screen reader support. You retain full control over markup and styling. Kobalte's API uses SolidJS primitives (signals, stores) and the compound component pattern, making it the idiomatic choice for accessible UI in Solid applications.

Setup & Configuration

Installation

npm install @kobalte/core

For Tailwind CSS users, install the Kobalte Tailwind plugin for styling data-attribute states:

npm install -D @kobalte/tailwindcss

Add to tailwind.config.js:

module.exports = {
  plugins: [require('@kobalte/tailwindcss')],
}

This enables classes like ui-expanded:bg-blue-100 that target Kobalte's data-expanded, data-disabled, and other state attributes.

Core Patterns

Compound component API

Kobalte components use a dot-notation compound pattern. Each sub-component is a namespace export:

import { Button } from '@kobalte/core/button'
import { Dialog } from '@kobalte/core/dialog'

function App() {
  return (
    <Dialog>
      <Dialog.Trigger class="px-4 py-2 bg-blue-600 text-white rounded">
        Open
      </Dialog.Trigger>
      <Dialog.Portal>
        <Dialog.Overlay class="fixed inset-0 bg-black/40" />
        <Dialog.Content class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white p-6 rounded-lg shadow-xl">
          <Dialog.Title class="text-lg font-semibold">Settings</Dialog.Title>
          <Dialog.Description class="text-gray-600 mt-2">
            Adjust your preferences below.
          </Dialog.Description>
          <Dialog.CloseButton class="absolute top-2 right-2">X</Dialog.CloseButton>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog>
  )
}

Select

import { Select } from '@kobalte/core/select'

const options = ['React', 'Solid', 'Svelte', 'Vue']

function FrameworkSelect() {
  return (
    <Select
      options={options}
      placeholder="Select a framework"
      itemComponent={(props) => (
        <Select.Item item={props.item} class="px-3 py-2 cursor-pointer ui-highlighted:bg-blue-100">
          <Select.ItemLabel>{props.item.rawValue}</Select.ItemLabel>
          <Select.ItemIndicator>✓</Select.ItemIndicator>
        </Select.Item>
      )}
    >
      <Select.Label class="text-sm font-medium">Framework</Select.Label>
      <Select.Trigger class="flex items-center border rounded px-3 py-2">
        <Select.Value<string>>{(state) => state.selectedOption()}</Select.Value>
        <Select.Icon class="ml-auto">▼</Select.Icon>
      </Select.Trigger>
      <Select.Portal>
        <Select.Content class="bg-white border rounded shadow-lg">
          <Select.Listbox class="py-1" />
        </Select.Content>
      </Select.Portal>
    </Select>
  )
}

Popover

import { Popover } from '@kobalte/core/popover'

function InfoPopover() {
  return (
    <Popover>
      <Popover.Trigger class="text-blue-600 underline">
        More info
      </Popover.Trigger>
      <Popover.Portal>
        <Popover.Content class="bg-white border rounded-lg shadow-lg p-4 max-w-xs">
          <Popover.Arrow />
          <Popover.Title class="font-semibold">Details</Popover.Title>
          <Popover.Description class="text-sm text-gray-600 mt-1">
            Additional information about this feature.
          </Popover.Description>
          <Popover.CloseButton class="absolute top-1 right-1">×</Popover.CloseButton>
        </Popover.Content>
      </Popover.Portal>
    </Popover>
  )
}

Controlled state with Solid signals

import { createSignal } from 'solid-js'
import { Checkbox } from '@kobalte/core/checkbox'

function ControlledCheckbox() {
  const [checked, setChecked] = createSignal(false)

  return (
    <Checkbox checked={checked()} onChange={setChecked} class="flex items-center gap-2">
      <Checkbox.Input />
      <Checkbox.Control class="w-5 h-5 border rounded ui-checked:bg-blue-600 ui-checked:border-blue-600">
        <Checkbox.Indicator>✓</Checkbox.Indicator>
      </Checkbox.Control>
      <Checkbox.Label>Accept terms</Checkbox.Label>
    </Checkbox>
  )
}

Styling with data attributes

Kobalte exposes component state via data-* attributes on rendered elements. You can target these with CSS or with the Tailwind plugin:

/* Plain CSS */
[data-expanded] { background-color: #eff6ff; }
[data-disabled] { opacity: 0.5; cursor: not-allowed; }
[data-highlighted] { background-color: #dbeafe; }
<!-- Tailwind with @kobalte/tailwindcss -->
<Select.Item class="ui-highlighted:bg-blue-100 ui-disabled:opacity-50">

Best Practices

  • Import from specific component paths (@kobalte/core/dialog) rather than the barrel export for better tree-shaking and faster builds.
  • Use the @kobalte/tailwindcss plugin to style states with utility classes instead of writing custom CSS selectors for data attributes.
  • Wrap content in <*.Portal> for overlays (dialogs, popovers, selects) to avoid z-index and overflow clipping issues.

Common Pitfalls

  • Forgetting <Select.Portal> around dropdown content, which causes the listbox to be clipped by parent containers with overflow: hidden.
  • Using standard HTML event handlers (onClick) instead of Kobalte's callback props (onChange, onOpenChange); Kobalte manages event delegation internally and the callback props ensure correct behavior with keyboard and assistive technology interactions.

Anti-Patterns

Using the service without understanding its pricing model. Cloud services bill differently — per request, per GB, per seat. Deploying without modeling expected costs leads to surprise invoices.

Hardcoding configuration instead of using environment variables. API keys, endpoints, and feature flags change between environments. Hardcoded values break deployments and leak secrets.

Ignoring the service's rate limits and quotas. Every external API has throughput limits. Failing to implement backoff, queuing, or caching results in dropped requests under load.

Treating the service as always available. External services go down. Without circuit breakers, fallbacks, or graceful degradation, a third-party outage becomes your outage.

Coupling your architecture to a single provider's API. Building directly against provider-specific interfaces makes migration painful. Wrap external services in thin adapter layers.

Install this skill directly: skilldb add ui-components-services-skills

Get CLI access →