# @f0rbit/ui - SolidJS Component Library
A minimal, composable UI component library for SolidJS.
## Installation
```bash
bun add @f0rbit/ui
```
```tsx
import "@f0rbit/ui/styles";
import { Button, Card } from "@f0rbit/ui";
```
## Components
### Button
A clickable button with multiple variants and sizes.
```tsx
import { Button } from "@f0rbit/ui";
```
Props:
- `variant`: "primary" | "secondary" | "ghost" | "danger" (default: "primary") — Visual style variant
- `size`: "sm" | "md" | "lg" (default: "md") — Button size
- `icon`: boolean (default: false) — Render as square icon-only button
- `label`: string — Accessible label for icon buttons (sets aria-label)
- `loading`: boolean (default: false) — Show loading spinner
- `disabled`: boolean (default: false) — Disable the button
Example:
```tsx
```
Accessibility:
- Always use the label prop for icon-only buttons (sets aria-label)
- Loading state announces to screen readers via aria-busy
- Disabled buttons are focusable but not interactive
Avoid:
- Forgetting label prop on icon buttons - screen readers won't announce the purpose
- Using loading without disabling form submission - can cause double submits
- Using ghost variant for primary actions - use primary for main CTAs
### Badge
A small label for categorization or status indication.
```tsx
import { Badge } from "@f0rbit/ui";
```
Props:
- `variant`: "default" | "success" | "error" | "warning" | "info" | "accent" (default: "default") — Visual style variant
Example:
```tsx
Default
```
Accessibility:
- Badge is purely visual - not announced separately by screen readers
- Content inside Badge is read as normal text
Avoid:
- Using Badge for interactive elements - use Button instead
- Relying on color alone to convey meaning - always include text
### Card
A container component for grouping related content.
```tsx
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@f0rbit/ui";
```
Props:
- `interactive`: boolean (default: false) — Enable hover effects for clickable cards
Example:
```tsx
Card TitleCard description textMain content goes hereFooter content
```
Accessibility:
- Interactive cards should have role='button' or be wrapped in a link
- Use CardTitle for proper heading hierarchy
Avoid:
- Using interactive without adding click handler or wrapping in link
- Nesting interactive cards - avoid nested clickable elements
- Missing CardHeader/CardContent structure - use sub-components for consistency
### Modal
A dialog overlay for focused user interactions.
```tsx
import { Modal, ModalHeader, ModalTitle, ModalBody, ModalFooter } from "@f0rbit/ui";
```
Props:
- `open`: boolean — Controls modal visibility
- `onClose`: () => void — Callback when modal is closed
Example:
```tsx
const [open, setOpen] = createSignal(false);
setOpen(false)}>
Modal TitleModal content
```
Accessibility:
- Focus is trapped inside modal when open
- ESC key closes the modal
- Clicking backdrop closes the modal
- Uses aria-modal and role='dialog'
- ModalTitle is announced as the dialog label
Avoid:
- Forgetting onClose handler - modal won't close on backdrop click or ESC
- Not providing ModalTitle - screen readers need a label for the dialog
- Putting autofocus elements without considering keyboard users
### Dropdown
A floating menu triggered by a button or element.
```tsx
import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, DropdownDivider } from "@f0rbit/ui";
```
Props:
- `children`: JSX.Element — Dropdown content (trigger and menu)
Example:
```tsx
console.log("Item 1")}>Item 1 console.log("Item 2")}>Item 2 console.log("Item 3")}>Item 3
```
Accessibility:
- Menu opens on click, closes on outside click or ESC
- Arrow keys navigate between items
- Enter/Space activates focused item
Avoid:
- Putting complex interactive content in DropdownItem - keep items simple
- Using for navigation - consider a proper nav component instead
- Forgetting DropdownTrigger wrapper around the trigger element
### Collapsible
An expandable/collapsible content section.
```tsx
import { Collapsible } from "@f0rbit/ui";
```
Props:
- `trigger`: JSX.Element | string — Content shown in the trigger area
- `defaultOpen`: boolean (default: false) — Initial open state (uncontrolled)
- `open`: boolean — Controlled open state
- `onOpenChange`: (open: boolean) => void — Callback when open state changes
Example:
```tsx
This content is collapsible.
```
Accessibility:
- Trigger is keyboard accessible (Enter/Space to toggle)
- Uses aria-expanded to announce state
- Content region is properly associated with trigger
Avoid:
- Using both defaultOpen and open props - pick controlled or uncontrolled
- Complex interactive content in trigger - keep trigger simple
### Stepper
A multi-step progress indicator.
```tsx
import { Stepper, Step } from "@f0rbit/ui";
```
Props:
- `orientation`: "horizontal" | "vertical" (default: "horizontal") — Layout direction of steps
Example:
```tsx
```
Accessibility:
- Steps announce their status via aria-current for current step
- Completed steps show checkmark for visual confirmation
Avoid:
- Making Step indicators clickable without implementing navigation
- Not updating status props when step changes
### Step
Individual step within a Stepper component.
```tsx
import { Step } from "@f0rbit/ui";
```
Props:
- `title`: string — Step title text
- `description`: string — Optional description below the title
- `icon`: JSX.Element — Custom icon for the step indicator
- `status`: "completed" | "current" | "upcoming" (default: "upcoming") — Current status of the step
Example:
```tsx
```
### Input
A text input field for single-line user input.
```tsx
import { Input } from "@f0rbit/ui";
```
Props:
- `error`: boolean (default: false) — Show error styling
- `disabled`: boolean (default: false) — Disable the input
- `placeholder`: string — Placeholder text
Example:
```tsx
```
Accessibility:
- Always pair with FormField for proper label association
- Error state is visual only - use FormField error prop for error message
- Use aria-describedby for additional context if not using FormField
Avoid:
- Using Input without FormField - missing accessible label
- Setting error={true} without showing error message
- Forgetting to match id prop with FormField id
### Textarea
A multi-line text input field.
```tsx
import { Textarea } from "@f0rbit/ui";
```
Props:
- `error`: boolean (default: false) — Show error styling
- `disabled`: boolean (default: false) — Disable the textarea
- `placeholder`: string — Placeholder text
Example:
```tsx
```
Accessibility:
- Always pair with FormField for proper label association
- Same accessibility considerations as Input
Avoid:
- Using Textarea without FormField - missing accessible label
### Select
A dropdown select input for choosing from options.
```tsx
import { Select } from "@f0rbit/ui";
```
Props:
- `error`: boolean (default: false) — Show error styling
- `disabled`: boolean (default: false) — Disable the select
Example:
```tsx
```
Accessibility:
- Uses native select element - fully accessible by default
- Pair with FormField for label association
Avoid:
- Using Select without FormField - missing accessible label
- Forgetting empty first option for placeholder
### Status
A status indicator with colored dot and label.
```tsx
import { Status } from "@f0rbit/ui";
```
Props:
- `state`: "active" | "inactive" | "error" | "pending" — Status state determining color and default label
- `label`: string — Custom label text (overrides default)
Example:
```tsx
```
Accessibility:
- Status label is always shown (not just color)
- Color is supplementary, not sole indicator
Avoid:
- Relying on color alone - always provide meaningful label
### Stat
A display component for statistics with value and label.
```tsx
import { Stat } from "@f0rbit/ui";
```
Props:
- `value`: string | number — The statistic value to display
- `label`: string — Label describing the statistic
Example:
```tsx
```
Accessibility:
- Label and value are properly associated for screen readers
Avoid:
- Using for interactive data - Stat is display only
### Spinner
A loading indicator animation.
```tsx
import { Spinner } from "@f0rbit/ui";
```
Props:
- `size`: "sm" | "md" | "lg" (default: "md") — Size of the spinner
Example:
```tsx
```
Accessibility:
- Respects prefers-reduced-motion (animation disabled)
- Add aria-label or sr-only text for screen readers when used standalone
Avoid:
- Using without accessible label - add sr-only text nearby
### Empty
A placeholder for empty states with optional icon and message.
```tsx
import { Empty } from "@f0rbit/ui";
```
Props:
- `icon`: JSX.Element — Icon to display above the message
- `title`: string — Main title text
- `description`: string — Additional description text
- `children`: JSX.Element — Additional content (e.g., action buttons)
Example:
```tsx
```
Accessibility:
- Title is a heading (h3) for proper document structure
- Action buttons remain fully accessible
Avoid:
- Using Empty for error states - consider a dedicated error component
### Clamp
A text clamping component with expand/collapse functionality.
```tsx
import { Clamp } from "@f0rbit/ui";
```
Props:
- `lines`: number (default: 3) — Maximum number of lines before clamping
- `showMoreText`: string (default: "show more") — Text for expand button
- `showLessText`: string (default: "show less") — Text for collapse button
Example:
```tsx
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco.
```
Accessibility:
- Expand/collapse button is keyboard accessible
- Full content is accessible when expanded
Avoid:
- Using with very short content - button shows when not needed
- Using lines={1} - consider truncate utility class instead
### Chevron
An animated chevron icon for expand/collapse indicators.
```tsx
import { Chevron } from "@f0rbit/ui";
```
Props:
- `expanded`: boolean (default: false) — Whether the chevron is in expanded state
- `facing`: "right" | "down" (default: "right") — Default direction the chevron points
- `size`: number | string (default: "1em") — Size of the chevron
Example:
```tsx
```
Accessibility:
- Decorative - use with proper ARIA on parent element
- Rotation animation respects prefers-reduced-motion
Avoid:
- Using as standalone button - wrap in button with aria-expanded
### Tabs
A tabbed interface for organizing content into separate views.
```tsx
import { Tabs, TabList, Tab, TabPanel } from "@f0rbit/ui";
```
Props:
- `defaultValue`: string — Initial active tab (uncontrolled)
- `value`: string — Controlled active tab value
- `onValueChange`: (value: string) => void — Callback when active tab changes
Example:
```tsx
Tab 1Tab 2Content 1Content 2
```
Accessibility:
- Arrow keys navigate between tabs
- Tab/Shift+Tab moves focus in/out of tab list
- Uses proper ARIA roles (tablist, tab, tabpanel)
- Active tab indicated via aria-selected
Avoid:
- Mismatched Tab value and TabPanel value - content won't show
- Using both defaultValue and value - pick controlled or uncontrolled
- Putting TabPanel outside of Tabs - context won't work
### Checkbox
A checkbox input for boolean or indeterminate selections.
```tsx
import { Checkbox } from "@f0rbit/ui";
```
Props:
- `checked`: boolean (default: false) — Whether the checkbox is checked
- `indeterminate`: boolean (default: false) — Show indeterminate state (partial selection)
- `label`: string — Label text displayed next to the checkbox
- `description`: string — Description text below the label
- `disabled`: boolean (default: false) — Disable the checkbox
- `onChange`: () => void — Callback when checkbox is toggled
Example:
```tsx
```
Accessibility:
- Label prop creates proper label association automatically
- Keyboard accessible - Space to toggle
- Indeterminate state announced by screen readers
Avoid:
- Using Checkbox without label prop - accessibility issue
- Forgetting onChange handler for controlled checkboxes
### Toggle
A switch component for toggling between on/off states.
```tsx
import { Toggle } from "@f0rbit/ui";
```
Props:
- `checked`: boolean (default: false) — Whether the toggle is on
- `label`: string — Label text displayed next to the toggle
- `description`: string — Description text below the label
- `size`: "sm" | "md" (default: "md") — Toggle size
- `disabled`: boolean (default: false) — Disable the toggle
- `onChange`: () => void — Callback when toggle is switched
Example:
```tsx
```
Accessibility:
- Uses role='switch' for proper screen reader announcement
- Space/Enter to toggle
- Label prop creates proper association
Avoid:
- Using Toggle without label - accessibility issue
- Using Toggle for multi-option selection - use Checkbox or radio instead
### FormField
A wrapper component for form inputs with label, description, and error states.
```tsx
import { FormField } from "@f0rbit/ui";
```
Props:
- `label`: string — Label text for the field
- `description`: string — Helper text below the input
- `error`: string — Error message to display
- `required`: boolean (default: false) — Show required indicator
- `id`: string — ID to associate label with input
Example:
```tsx
```
Accessibility:
- Automatically associates label with input via htmlFor/id
- Error messages are linked via aria-describedby
- Required indicator shown visually and via aria-required
Avoid:
- Mismatched id between FormField and Input - label won't associate
- Setting error on FormField but forgetting error prop on Input - visual mismatch
### Timeline
A vertical timeline for displaying chronological events.
```tsx
import { Timeline, type TimelineItem } from "@f0rbit/ui";
```
Props:
- `items`: TimelineItem[] — Array of timeline items to display
Example:
```tsx
```
Accessibility:
- Uses list semantics for proper screen reader navigation
- Timestamps are properly associated with events
Avoid:
- Large datasets without virtualization - consider pagination
### TabList
Container for Tab components within Tabs.
```tsx
import { TabList } from "@f0rbit/ui";
```
Props:
- `children`: JSX.Element — Tab components
### Tab
Individual tab button within TabList.
```tsx
import { Tab } from "@f0rbit/ui";
```
Props:
- `value`: string — Unique identifier for this tab
- `children`: JSX.Element — Tab label content
### TabPanel
Content panel shown when its corresponding tab is active.
```tsx
import { TabPanel } from "@f0rbit/ui";
```
Props:
- `value`: string — Must match a Tab value to associate content
- `children`: JSX.Element — Panel content
## Utility Classes
### Layout - Stack (Vertical Flex)
- `.stack` — Vertical flex with medium gap (--space-md)
- `.stack-sm` — Vertical flex with small gap (--space-sm)
- `.stack-lg` — Vertical flex with large gap (--space-lg)
### Layout - Row (Horizontal Flex)
- `.row` — Horizontal flex, centered items, small gap
- `.row-sm` — Row with extra small gap
- `.row-lg` — Row with medium gap
- `.row-between` — Row with space-between
- `.row-end` — Row with flex-end alignment
- `.row-start` — Row with flex-start alignment
### Layout - Grid
- `.grid` — Auto-fit grid with 250px min column width
- `.grid-2` — Fixed 2-column grid
- `.grid-3` — Fixed 3-column grid
- `.grid-4` — Fixed 4-column grid
### Layout - Other
- `.cluster` — Flex wrap with small gap, centered items
- `.center` — Max-width container (65ch) with auto margins
- `.center-narrow` — Narrow center (45ch)
- `.center-wide` — Wide center (90ch)
### Typography - Colors
- `.text-primary` — Primary foreground color (--fg)
- `.text-muted` — Muted text color (--fg-muted)
- `.text-subtle` — Subtle text color (--fg-subtle)
- `.text-faint` — Faint text color (--fg-faint)
- `.text-accent` — Accent color (--accent)
### Typography - Sizes
- `.text-xs` — Extra small text
- `.text-sm` — Small text
- `.text-base` — Base text size
- `.text-lg` — Large text
- `.text-xl` — Extra large text
- `.text-2xl` — 2x large text
- `.text-3xl` — 3x large text
- `.text-4xl` — 4x large text
### Typography - Weight & Style
- `.font-medium` — Medium font weight (500)
- `.font-bold` — Bold font weight (700)
- `.font-mono` — Monospace font family
- `.truncate` — Single-line text with ellipsis overflow
### Lists
- `.list` — Styled list with proper spacing and markers
### Visibility & Interaction
- `.sr-only` — Screen reader only (visually hidden)
- `.hidden` — Display none
- `.hover-reveal` — Hidden until parent is hovered/focused
- `.interactive` — Border color transition on hover
- `.interactive-color` — Text color transition on hover
- `.hide-mobile` — Hidden on mobile (max-width: 768px)
### Animation
- `.animate-spin` — Continuous rotation animation
- `.animate-pulse` — Pulsing opacity animation
## Common Patterns
### Form with Validation
```tsx
```
### Confirmation Modal
```tsx
setOpen(false)}>
Confirm DeleteAre you sure you want to delete this item?
```
### Card Grid
```tsx
```
### Loading State
```tsx
Loading...
}>
```
### Empty State with Action
```tsx
}
title="No items yet"
description="Get started by creating your first item"
>
```
### Settings Toggle List
```tsx
Notifications
toggle('email')} />
toggle('push')} />
```
## Accessibility Quick Reference
- **Button**: Use label prop for icon-only buttons; loading announces via aria-busy
- **Modal**: Focus trap enabled; ESC closes; uses role='dialog' and aria-modal
- **Dropdown**: Arrow keys navigate; ESC closes; Enter/Space activates
- **Tabs**: Arrow keys switch tabs; proper ARIA roles (tablist, tab, tabpanel)
- **Checkbox/Toggle**: Always provide label prop; Space to toggle
- **Input/Textarea/Select**: Always wrap in FormField for label association
- **Collapsible**: Uses aria-expanded; Enter/Space to toggle
- **Spinner**: Respects prefers-reduced-motion; add sr-only text for context