Kobalte
Kobalte: accessible, unstyled UI component library for SolidJS with WAI-ARIA compliance and composable compound component APIs
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 linesKobalte — 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/tailwindcssplugin 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 withoverflow: 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
Related Skills
Ark UI
"Ark UI: state machine-driven components, headless primitives, Select, Combobox, DatePicker, Toast, styling with any CSS framework"
Daisyui
"daisyUI: Tailwind CSS component library, themes, responsive utilities, component classes, customization, semantic color names"
Headless UI
"Headless UI: unstyled accessible components for Tailwind, Dialog, Combobox, Listbox, Menu, Transition, render props, React/Vue"
Melt UI
Melt UI: headless, accessible UI component builders for Svelte using the builder pattern with full styling freedom
Park UI
Park UI: pre-designed styled components built on Ark UI primitives, available for React, Vue, and Solid with Panda CSS or Tailwind CSS styling
Radix UI
"Radix UI: accessible primitives, Dialog, Dropdown, Popover, Tabs, Toast, composition pattern, styling with className, controlled/uncontrolled"