@lui-craft/ui
v1.0.2
Published
Modern, accessible, developer-first React component library
Downloads
349
Maintainers
Readme
@lui-craft/ui
A modern, accessible React component library with a Swiss-style design aesthetic. Part of the @lui-craft design system.
Installation
npm install @lui-craft/uiPeer dependencies — install these if not already in your project:
npm install react react-domSetup
1. Import the stylesheet
In your app entry point (e.g. main.tsx):
import '@lui-craft/ui/styles'2. Wrap with ThemeProvider
import { ThemeProvider } from '@lui-craft/ui'
export default function App() {
return (
<ThemeProvider theme="auto">
{/* your app */}
</ThemeProvider>
)
}theme accepts "light" | "dark" | "auto" (follows system preference).
3. Add ToastProvider if using Toast
import { ThemeProvider, ToastProvider } from '@lui-craft/ui'
export default function App() {
return (
<ThemeProvider theme="auto">
<ToastProvider>
{/* your app */}
</ToastProvider>
</ThemeProvider>
)
}Components
Button
import { Button } from '@lui-craft/ui'
<Button variant="primary">Save</Button>
<Button variant="secondary" size="sm" loading>Processing</Button>
<Button variant="danger" leftIcon={<TrashIcon />}>Delete</Button>| Prop | Type | Default |
|---|---|---|
| variant | primary | secondary | ghost | danger | primary |
| size | sm | md | lg | md |
| loading | boolean | false |
| fullWidth | boolean | false |
| leftIcon | ReactNode | — |
| rightIcon | ReactNode | — |
Input
import { Input } from '@lui-craft/ui'
<Input label="Email" type="email" placeholder="[email protected]" />
<Input label="Amount" suffix="USD" status="error" hint="Required field" />| Prop | Type | Default |
|---|---|---|
| label | string | — |
| hint | string | — |
| status | default | error | success | warning | default |
| size | sm | md | lg | md |
| prefix | ReactNode | — |
| suffix | ReactNode | — |
Card
import { Card } from '@lui-craft/ui'
<Card variant="raised">
<Card.Header title="Title" />
<Card.Body>Content goes here.</Card.Body>
<Card.Footer>
<Button size="sm">Action</Button>
</Card.Footer>
</Card>
{/* Or simple padding */}
<Card padding="lg" clickable onClick={handleClick}>
Simple card
</Card>| Prop | Type | Default |
|---|---|---|
| variant | default | subtle | raised | default |
| padding | none | sm | md | lg | md |
| clickable | boolean | false |
Badge
import { Badge } from '@lui-craft/ui'
<Badge variant="success">Active</Badge>
<Badge variant="danger" size="sm">Error</Badge>
<Badge variant="warning" dot />| Prop | Type | Default |
|---|---|---|
| variant | primary | secondary | success | warning | danger | ghost | primary |
| size | sm | md | md |
| dot | boolean | false |
| leftIcon | ReactNode | — |
| rightIcon | ReactNode | — |
Avatar
import { Avatar } from '@lui-craft/ui'
<Avatar src="/photo.jpg" alt="Jane Smith" size="md" />
<Avatar name="Jane Smith" size="lg" /> {/* initials fallback */}
<Avatar size="md" shape="square" /> {/* icon fallback */}| Prop | Type | Default |
|---|---|---|
| src | string | — |
| alt | string | "" |
| name | string | — |
| size | sm | md | lg | xl | md |
| shape | circle | square | circle |
Checkbox
import { Checkbox } from '@lui-craft/ui'
<Checkbox id="terms" label="Accept terms" checked={checked} onChange={e => setChecked(e.target.checked)} />
<Checkbox id="partial" label="Select all" indeterminate />| Prop | Type | Default |
|---|---|---|
| label | ReactNode | — |
| indeterminate | boolean | false |
Extends all native <input type="checkbox"> props.
Radio + RadioGroup
import { Radio, RadioGroup } from '@lui-craft/ui'
<RadioGroup name="plan" value={plan} onChange={setPlan} label="Billing plan">
<Radio value="monthly" label="Monthly" />
<Radio value="yearly" label="Yearly" />
<Radio value="custom" label="Custom" disabled />
</RadioGroup>RadioGroup props:
| Prop | Type | Default |
|---|---|---|
| name | string | required |
| value | string | — |
| onChange | (value: string) => void | — |
| orientation | horizontal | vertical | vertical |
| disabled | boolean | false |
Toggle
import { Toggle } from '@lui-craft/ui'
<Toggle id="notifications" label="Enable notifications" checked={on} onChange={e => setOn(e.target.checked)} />
<Toggle id="compact" label="Compact mode" size="sm" labelPlacement="left" />| Prop | Type | Default |
|---|---|---|
| size | sm | md | md |
| label | ReactNode | — |
| labelPlacement | left | right | right |
Extends all native <input type="checkbox"> props.
Select
import { Select } from '@lui-craft/ui'
const options = [
{ value: 'design', label: 'Design' },
{ value: 'eng', label: 'Engineering' },
{ value: 'ops', label: 'Operations', disabled: true },
]
<Select
label="Department"
options={options}
value={dept}
onChange={setDept}
placeholder="Choose…"
hint="Select your team"
/>| Prop | Type | Default |
|---|---|---|
| options | SelectOption[] | required |
| value | string | — |
| onChange | (value: string) => void | — |
| placeholder | string | "Select…" |
| size | sm | md | lg | md |
| status | default | error | success | warning | default |
| label | string | — |
| hint | string | — |
| disabled | boolean | false |
Tooltip
import { Tooltip } from '@lui-craft/ui'
<Tooltip content="Save changes" placement="top">
<Button variant="ghost" aria-label="Save"><SaveIcon /></Button>
</Tooltip>| Prop | Type | Default |
|---|---|---|
| content | ReactNode | required |
| placement | top | bottom | left | right | top |
| delay | number (ms) | 300 |
| disabled | boolean | false |
Modal
import { Modal } from '@lui-craft/ui'
<Modal open={open} onClose={() => setOpen(false)} size="md">
<Modal.Header title="Confirm deletion" onClose={() => setOpen(false)} />
<Modal.Body>
Are you sure? This action cannot be undone.
</Modal.Body>
<Modal.Footer>
<Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="danger" onClick={handleDelete}>Delete</Button>
</Modal.Footer>
</Modal>| Prop | Type | Default |
|---|---|---|
| open | boolean | required |
| onClose | () => void | required |
| size | sm | md | lg | fullscreen | md |
| closeOnBackdrop | boolean | true |
| ariaLabel | string | — |
Tabs
import { Tabs, TabList, Tab, TabPanel } from '@lui-craft/ui'
<Tabs value={tab} onChange={setTab}>
<TabList ariaLabel="Settings sections">
<Tab value="general">General</Tab>
<Tab value="security">Security</Tab>
<Tab value="billing">Billing</Tab>
</TabList>
<TabPanel value="general">…</TabPanel>
<TabPanel value="security">…</TabPanel>
<TabPanel value="billing">…</TabPanel>
</Tabs>| Prop | Type | Default |
|---|---|---|
| value | string | required |
| onChange | (value: string) => void | required |
| orientation | horizontal | vertical | horizontal |
Keyboard: ← → (or ↑ ↓ for vertical), Home, End to navigate tabs.
Toast
import { ToastProvider, useToast } from '@lui-craft/ui'
// In your component:
const { show, dismiss } = useToast()
show({ message: 'Saved!', type: 'success' })
show({ message: 'Failed to connect.', type: 'error', duration: 8000 })
show({
message: 'Update available.',
type: 'info',
action: { label: 'Refresh', onClick: () => window.location.reload() },
})show() options:
| Option | Type | Default |
|---|---|---|
| message | ReactNode | required |
| type | success | error | warning | info | — |
| duration | number (ms) | 4000 |
| action | { label: string; onClick: () => void } | — |
Table
import { Table } from '@lui-craft/ui'
import type { TableColumn } from '@lui-craft/ui'
const columns: TableColumn<User>[] = [
{ key: 'name', header: 'Name', sortable: true },
{ key: 'email', header: 'Email', sortable: true },
{ key: 'role', header: 'Role', align: 'center',
render: (val) => <Badge variant="ghost">{String(val)}</Badge> },
]
<Table
columns={columns}
data={users}
striped
stickyHeader
emptyState="No users found."
/>| Prop | Type | Default |
|---|---|---|
| columns | TableColumn[] | required |
| data | T[] | required |
| loading | boolean | false |
| emptyState | ReactNode | "No data" |
| striped | boolean | false |
| bordered | boolean | false |
| stickyHeader | boolean | false |
| sortKey | string | — |
| sortDir | asc | desc | — |
| onSortChange | (key, dir) => void | — |
SidebarMenu
import { SidebarMenu } from '@lui-craft/ui'
import type { MenuItem } from '@lui-craft/ui'
const items: MenuItem[] = [
{ id: 'home', label: 'Home', icon: <HomeIcon />, href: '/' },
{
id: 'settings', label: 'Settings', icon: <SettingsIcon />,
children: [
{ id: 'profile', label: 'Profile', icon: <UserIcon />, href: '/settings/profile' },
{ id: 'security', label: 'Security', icon: <LockIcon />, href: '/settings/security' },
],
},
]
<SidebarMenu
items={items}
activeId={activeId}
onNavigate={item => setActiveId(item.id)}
header={<Logo />}
footer={<UserFooter />}
/>| Prop | Type | Default |
|---|---|---|
| items | MenuItem[] | required |
| activeId | string | — |
| defaultCollapsed | boolean | false |
| onNavigate | (item: MenuItem) => void | — |
| header | ReactNode | — |
| footer | ReactNode | — |
| collapseIcon | ReactNode | built-in chevron |
| expandIcon | ReactNode | built-in chevron |
| menuIcon | ReactNode | built-in hamburger |
| closeIcon | ReactNode | built-in × |
Theming
All styles use CSS custom properties scoped to [data-lui-theme]. Override any token after importing the stylesheet:
/* Override in your global CSS */
:root {
--lui-color-primary: #0F4C81;
--lui-color-primary-hover: #1A6BBF;
--lui-radius: 4px;
}Available token groups:
| Group | Prefix | Examples |
|---|---|---|
| Primary color | --lui-color-primary* | -primary, -primary-hover, -primary-fg |
| Danger | --lui-color-danger* | -danger, -danger-hover, -danger-fg |
| Success | --lui-color-success* | -success, -success-fg |
| Warning | --lui-color-warning* | -warning, -warning-fg |
| Surface | --lui-color-surface* | -surface, -surface-subtle, -surface-raised |
| Border | --lui-color-border* | -border, -border-strong, -border-focus |
| Text | --lui-color-text* | -text, -text-muted, -text-subtle, -text-inverse |
| Spacing | --lui-spacing-* | 1 (4px) → 8 (32px) |
| Typography | --lui-font-size-* | xs (11px) → lg (15px) |
| Shape | --lui-radius, --lui-radius-sm | 2px, 1px |
| Motion | --lui-transition | 120ms ease |
| Shadow | --lui-shadow-* | sm, md, lg |
Dark mode is automatic when theme="auto" or theme="dark" is set on ThemeProvider. All tokens remap under [data-lui-theme='dark'].
Companion package
Icons are available separately:
npm install @lui-craft/luiconimport { LuHome, LuSettings, LuIconProvider } from '@lui-craft/luicon'
<LuIconProvider config={{ strokeWidth: 2, theme: 'auto' }}>
<LuHome variant="outline" size={20} />
</LuIconProvider>License
MIT © Nittanando Sarkar
