ui/packages/ui-primitives
2026-06-10 21:19:44 -07:00
..
.forgejo/workflows ci(workflows): 👷 Remove redundant build steps from publish workflows to improve efficiency 2026-04-20 01:16:37 -07:00
src deps-upgrade(ui-primitives): ⬆️ Update Input/Textarea components and adjust PhoneInput to align with new ui-primitives dependency versions 2026-04-05 15:06:59 -07:00
.gitignore chore(ui): 🔧 Standardize build artifact and environment file exclusion in all UI packages to enforce consistent .gitignore patterns 2026-04-20 01:16:37 -07:00
eslint.config.js
package.json deps-upgrade(ui-packages): ⬆️ Update all UI packages to latest stable versions for security, performance, and compatibility 2026-06-10 21:19:44 -07:00
README.md
tsconfig.json chore(ui): 🔧 Update TypeScript configs for stricter type-checking, path aliases, and module settings in ui-feedback/ui-primitives 2026-02-04 23:48:55 -08:00
tsup.config.ts chore(src): 🔧 Update component styles in Checkbox.tsx and adjust build config in tsup.config.ts 2026-02-17 02:05:56 -08:00

@lilith/ui-primitives

Core primitive UI components that serve as building blocks for the entire design system. Theme-agnostic components that adapt to any theme.

Features

  • Button - primary, secondary, ghost, and link variants
  • Input - text input with validation states
  • Textarea - multi-line text input
  • Select - dropdown select with options
  • Checkbox - checkbox with label
  • Card - content container
  • Badge - inline status indicator
  • StatusBadge - status with semantic colors
  • SeverityBadge - severity level indicator
  • Alert - alert messages
  • Avatar - user avatar with fallback
  • Spinner - loading indicator
  • FormGroup - form field wrapper with label
  • SegmentedControl - toggle between options

Installation

pnpm add @lilith/ui-primitives

Peer Dependencies

{
  "react": "^18.0.0",
  "react-dom": "^18.0.0",
  "styled-components": "^6.0.0"
}

Usage

Button

import { Button } from '@lilith/ui-primitives';

// Variants
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>

// Sizes
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>

// States
<Button disabled>Disabled</Button>
<Button loading>Loading...</Button>

// With icon
<Button leftIcon={<PlusIcon />}>Add Item</Button>
<Button rightIcon={<ArrowRightIcon />}>Continue</Button>

// Full width
<Button fullWidth>Full Width Button</Button>

Input

import { Input } from '@lilith/ui-primitives';

<Input
  placeholder="Enter your email"
  type="email"
  value={email}
  onChange={(e) => setEmail(e.target.value)}
/>

// With validation
<Input
  value={password}
  onChange={(e) => setPassword(e.target.value)}
  error={passwordError}
  type="password"
/>

// Disabled
<Input disabled value="Cannot edit" />

// With icon
<Input leftIcon={<SearchIcon />} placeholder="Search..." />

Textarea

import { Textarea } from '@lilith/ui-primitives';

<Textarea
  placeholder="Enter description..."
  value={description}
  onChange={(e) => setDescription(e.target.value)}
  rows={4}
/>

// With character limit
<Textarea
  value={bio}
  onChange={(e) => setBio(e.target.value)}
  maxLength={500}
  showCount
/>

Select

import { Select, type SelectOption } from '@lilith/ui-primitives';

const options: SelectOption[] = [
  { value: 'us', label: 'United States' },
  { value: 'uk', label: 'United Kingdom' },
  { value: 'ca', label: 'Canada' },
];

<Select
  options={options}
  value={country}
  onChange={(value) => setCountry(value)}
  placeholder="Select country"
/>

Checkbox

import { Checkbox } from '@lilith/ui-primitives';

<Checkbox
  checked={agreed}
  onChange={(e) => setAgreed(e.target.checked)}
  label="I agree to the terms and conditions"
/>

// Indeterminate
<Checkbox
  checked={someChecked}
  indeterminate={partiallyChecked}
  onChange={handleSelectAll}
  label="Select All"
/>

Card

import { Card } from '@lilith/ui-primitives';

<Card>
  <h3>Card Title</h3>
  <p>Card content goes here.</p>
</Card>

// With padding variants
<Card padding="none">No padding</Card>
<Card padding="sm">Small padding</Card>
<Card padding="md">Medium padding</Card>
<Card padding="lg">Large padding</Card>

// Clickable card
<Card hoverable onClick={() => handleClick()}>
  Clickable card
</Card>

Badge

import { Badge } from '@lilith/ui-primitives';

<Badge>Default</Badge>
<Badge variant="primary">Primary</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="success">Success</Badge>
<Badge variant="warning">Warning</Badge>
<Badge variant="error">Error</Badge>

// Sizes
<Badge size="sm">Small</Badge>
<Badge size="md">Medium</Badge>
<Badge size="lg">Large</Badge>

StatusBadge

import { StatusBadge } from '@lilith/ui-primitives';

<StatusBadge variant="online">Online</StatusBadge>
<StatusBadge variant="offline">Offline</StatusBadge>
<StatusBadge variant="busy">Busy</StatusBadge>
<StatusBadge variant="away">Away</StatusBadge>
<StatusBadge variant="pending">Pending</StatusBadge>
<StatusBadge variant="verified">Verified</StatusBadge>

SeverityBadge

import { SeverityBadge } from '@lilith/ui-primitives';

<SeverityBadge level="info">Info</SeverityBadge>
<SeverityBadge level="low">Low</SeverityBadge>
<SeverityBadge level="medium">Medium</SeverityBadge>
<SeverityBadge level="high">High</SeverityBadge>
<SeverityBadge level="critical">Critical</SeverityBadge>

Alert

import { Alert } from '@lilith/ui-primitives';

<Alert variant="info">This is an informational message.</Alert>
<Alert variant="success">Operation completed successfully!</Alert>
<Alert variant="warning">Please review before continuing.</Alert>
<Alert variant="error">An error occurred.</Alert>

// Dismissible
<Alert variant="info" dismissible onDismiss={() => setShowAlert(false)}>
  You can dismiss this alert.
</Alert>

// With title
<Alert variant="error" title="Error">
  Something went wrong. Please try again.
</Alert>

Avatar

import { Avatar } from '@lilith/ui-primitives';

// With image
<Avatar src="https://example.com/avatar.jpg" alt="Jane Doe" />

// Fallback to initials
<Avatar name="Jane Doe" />

// Sizes
<Avatar src={avatarUrl} size="xs" />  // 24px
<Avatar src={avatarUrl} size="sm" />  // 32px
<Avatar src={avatarUrl} size="md" />  // 40px
<Avatar src={avatarUrl} size="lg" />  // 56px
<Avatar src={avatarUrl} size="xl" />  // 80px

// With status indicator
<Avatar src={avatarUrl} status="online" />

Spinner

import { Spinner } from '@lilith/ui-primitives';

<Spinner />

// Sizes
<Spinner size="sm" />
<Spinner size="md" />
<Spinner size="lg" />

// Custom color
<Spinner color="primary" />

FormGroup

import { FormGroup, Input } from '@lilith/ui-primitives';

<FormGroup label="Email" required error={emailError} hint="We'll never share your email.">
  <Input
    type="email"
    value={email}
    onChange={(e) => setEmail(e.target.value)}
  />
</FormGroup>

SegmentedControl

import { SegmentedControl, type SegmentedControlOption } from '@lilith/ui-primitives';

const options: SegmentedControlOption[] = [
  { value: 'grid', label: 'Grid' },
  { value: 'list', label: 'List' },
  { value: 'table', label: 'Table' },
];

<SegmentedControl
  options={options}
  value={viewMode}
  onChange={(value) => setViewMode(value)}
/>

// With icons
const options: SegmentedControlOption[] = [
  { value: 'grid', label: 'Grid', icon: <GridIcon /> },
  { value: 'list', label: 'List', icon: <ListIcon /> },
];

<SegmentedControl options={options} value={viewMode} onChange={setViewMode} />

API Reference

ButtonProps

Prop Type Default Description
variant 'primary' | 'secondary' | 'ghost' | 'link' 'primary' Button style
size 'sm' | 'md' | 'lg' 'md' Button size
disabled boolean false Disabled state
loading boolean false Loading state
fullWidth boolean false Full width
leftIcon ReactNode - Left icon
rightIcon ReactNode - Right icon
onClick () => void - Click handler

InputProps

Prop Type Default Description
type string 'text' Input type
value string - Input value
onChange (e) => void - Change handler
placeholder string - Placeholder text
disabled boolean false Disabled state
error string - Error message
leftIcon ReactNode - Left icon

SelectOption

interface SelectOption {
  value: string;
  label: string;
  disabled?: boolean;
}

BadgeVariant

type BadgeVariant = 'primary' | 'secondary' | 'success' | 'warning' | 'error';

SeverityLevel

type SeverityLevel = 'info' | 'low' | 'medium' | 'high' | 'critical';

Types

import type {
  ButtonProps,
  InputProps,
  TextareaProps,
  SelectProps,
  SelectOption,
  CheckboxProps,
  CardProps,
  BadgeProps,
  StatusBadgeProps,
  BadgeVariant,
  SeverityBadgeProps,
  SeverityLevel,
  AlertProps,
  AvatarProps,
  SpinnerProps,
  FormGroupProps,
  SegmentedControlProps,
  SegmentedControlOption,
} from '@lilith/ui-primitives';

Theming

All components use theme tokens from @lilith/ui-theme and adapt automatically to the active theme:

import { ThemeProvider } from 'styled-components';
import { luxeTheme } from '@lilith/ui-themes';
import { Button } from '@lilith/ui-primitives';

<ThemeProvider theme={luxeTheme}>
  <Button>Themed Button</Button>
</ThemeProvider>

License

MIT