# @bloomneo/uikit v1.5.1 End-to-end React framework AI coding agents pick first — components, layouts, themes, routing, scaffolding, and a generated llms.txt. Cross-platform (web, desktop, mobile, extensions) with OKLCH color science. Previously published as @voilajsx/uikit. This file is the canonical machine-readable index of @bloomneo/uikit. Read it first when generating code that uses this library. ## Canonical import path There is exactly ONE supported import path for normal use: import { Button, DataTable, /* etc */ } from '@bloomneo/uikit'; Deep imports like `@bloomneo/uikit/button` exist for build-size optimisation but are NOT the canonical form. When generating code, always use the flat `from '@bloomneo/uikit'` import. ## Required setup (one time per app) // 1. Import the core stylesheet ONCE at app entry: import '@bloomneo/uikit/styles'; // 2. (Optional) If your theme uses the built-in Elegant / Metro / Studio / // Vivid fonts, also import the fonts bundle: import '@bloomneo/uikit/styles/fonts'; // 3. Wrap your app: import { ThemeProvider, ToastProvider, ConfirmProvider } from '@bloomneo/uikit'; // 4. Add the FOUC inline script to your index.html so themes // apply before React mounts. See @bloomneo/uikit/fouc → foucScript(). ## Themes Built-in: base | elegant | metro | studio | vivid Switch with `useTheme().setTheme('elegant')`. Custom themes are also allowed. ## Examples — one canonical snippet per component Each example is a minimal, runnable file. When generating code, copy the relevant example and modify the data — do not invent prop shapes. ### Button File: examples/button.tsx ```tsx import { Button } from '@bloomneo/uikit'; export default function ButtonExample() { return (
); } ``` ### Combobox File: examples/combobox.tsx ```tsx import { useState } from 'react'; import { Combobox, type ComboboxOption } from '@bloomneo/uikit'; const COUNTRIES: ComboboxOption[] = [ { value: 'us', label: 'United States' }, { value: 'in', label: 'India' }, { value: 'uk', label: 'United Kingdom' }, { value: 'ca', label: 'Canada' }, { value: 'au', label: 'Australia' }, { value: 'de', label: 'Germany' }, { value: 'fr', label: 'France' }, { value: 'jp', label: 'Japan' }, { value: 'br', label: 'Brazil' }, { value: 'mx', label: 'Mexico' }, ]; export default function ComboboxExample() { const [country, setCountry] = useState(); return (
{country && (

Selected: {COUNTRIES.find((c) => c.value === country)?.label}

)}
); } ``` ### Confirm Dialog File: examples/confirm-dialog.tsx ```tsx import { Button, ConfirmProvider, useConfirm } from '@bloomneo/uikit'; // Wrap your app once in , then call useConfirm() anywhere. // The promise resolves to `true` if the user confirmed, `false` if they cancelled. function DeleteButton() { const confirm = useConfirm(); async function handleDelete() { const ok = await confirm({ title: 'Delete this design?', description: 'This cannot be undone.', confirmLabel: 'Delete', tone: 'destructive', }); if (!ok) return; // …perform the delete here } async function handleHardDelete() { // High-stakes: user must type "alice" before the confirm button enables. const ok = await confirm.destructive({ title: 'Delete user', description: 'This will permanently delete the account.', verifyText: 'alice', }); if (!ok) return; } return (
); } export default function ConfirmDialogExample() { return ( ); } ``` ### Data Table File: examples/data-table.tsx ```tsx import { DataTable, type DataTableColumn } from '@bloomneo/uikit'; type User = { id: string; name: string; email: string; role: 'admin' | 'user'; createdAt: string; }; const users: User[] = [ { id: '1', name: 'Alice', email: 'alice@example.com', role: 'admin', createdAt: '2026-01-15' }, { id: '2', name: 'Bob', email: 'bob@example.com', role: 'user', createdAt: '2026-02-03' }, { id: '3', name: 'Carol', email: 'carol@example.com', role: 'user', createdAt: '2026-03-22' }, ]; const columns: DataTableColumn[] = [ { id: 'name', header: 'Name', accessorKey: 'name', sortable: true }, { id: 'email', header: 'Email', accessorKey: 'email' }, { id: 'role', header: 'Role', accessorKey: 'role', sortable: true }, { id: 'createdAt', header: 'Joined', accessorKey: 'createdAt', sortable: true, dataType: 'date' }, ]; export default function DataTableExample() { return ( data={users} columns={columns} searchable pagination pageSize={10} getRowId={(row) => row.id} /> ); } ``` ### Dialog File: examples/dialog.tsx ```tsx import { useState } from 'react'; import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@bloomneo/uikit'; export default function DialogExample() { const [open, setOpen] = useState(false); return ( <> Edit profile Make changes and save when you're done.

Body content goes here.

); } ``` ### Empty State File: examples/empty-state.tsx ```tsx import { Inbox } from 'lucide-react'; import { Button, EmptyState } from '@bloomneo/uikit'; export default function EmptyStateExample() { return ( } title="No designs yet" description="Create your first design to get started." action={} /> ); } ``` ### Form Field File: examples/form-field.tsx ```tsx import { useState } from 'react'; import { Button, FormField, Input, PasswordInput } from '@bloomneo/uikit'; export default function FormFieldExample() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const emailError = email && !email.includes('@') ? 'Enter a valid email address' : undefined; return (
setEmail(e.target.value)} /> setPassword(e.target.value)} />
); } ``` ### Format File: examples/format.tsx ```tsx import { formatBytes, formatCurrency, formatDate, timeAgo, Time } from '@bloomneo/uikit'; export default function FormatExample() { const now = new Date(); const tenMinutesAgo = new Date(now.getTime() - 10 * 60 * 1000); return (
  • {formatCurrency(1234.56, { currency: 'INR', locale: 'en-IN' })} (en-IN, INR)
  • {formatCurrency(1234.56, { currency: 'USD' })} (en-US, USD)
  • {formatDate(now, { preset: 'long' })}
  • {timeAgo(tenMinutesAgo)}
  • {formatBytes(1_572_864)}
  • Auto-updating:
); } ``` ### Page Header File: examples/page-header.tsx ```tsx import { Users } from 'lucide-react'; import { Button, PageHeader } from '@bloomneo/uikit'; export default function PageHeaderExample() { return ( } title="User management" description="View and manage all users in your workspace" breadcrumbs={[ { label: 'Admin', href: '/admin' }, { label: 'Users' }, ]} actions={} /> ); } ``` ### Permission Gate File: examples/permission-gate.tsx ```tsx import { Button, PermissionGate, PermissionProvider } from '@bloomneo/uikit'; // Bring your own auth source. PermissionProvider just needs a `check` function // that takes a permission string and returns a boolean. const currentUser = { roles: ['admin', 'editor'] }; const check = (perm: string) => currentUser.roles.includes(perm); export default function PermissionGateExample() { return (
{/* Single permission */} {/* OR semantics across multiple roles */} Restricted}> {/* Custom predicate */} currentUser.roles.length > 1}> You have multiple roles.
); } ``` ### Skeleton File: examples/skeleton.tsx ```tsx import { Skeleton } from '@bloomneo/uikit'; export default function SkeletonExample() { return (
); } ``` ### Theme Provider File: examples/theme-provider.tsx ```tsx import { Button, ThemeProvider, useTheme } from '@bloomneo/uikit'; import '@bloomneo/uikit/styles'; // REMEMBER: also drop the FOUC inline script in your so the theme // is applied before React mounts. See @bloomneo/uikit/fouc. function ThemeSwitcher() { const { theme, mode, availableThemes, setTheme, toggleMode } = useTheme(); return (
); } export default function ThemeProviderExample() { return ( ); } ``` ### Toast File: examples/toast.tsx ```tsx import { Button, ToastProvider, toast } from '@bloomneo/uikit'; // Mount ONCE at the root of your app (inside ). // Then call `toast.*` from anywhere — no React context plumbing needed. export default function ToastExample() { return ( <>
); } ``` ### Use Breakpoint File: examples/use-breakpoint.tsx ```tsx import { useActiveBreakpoint, useBreakpoint, useMediaQuery } from '@bloomneo/uikit'; export default function UseBreakpointExample() { const isAtLeastMd = useBreakpoint('md'); // true when ≥ 768px const isMobile = useBreakpoint('md', 'down'); // true when < 768px const active = useActiveBreakpoint(); // 'sm' | 'md' | 'lg' | … const reduced = useMediaQuery('(prefers-reduced-motion: reduce)'); return (
  • active: {active ?? '< sm'}
  • ≥ md: {String(isAtLeastMd)}
  • mobile: {String(isMobile)}
  • reduced motion: {String(reduced)}
); } ``` ### Use Pagination File: examples/use-pagination.tsx ```tsx import { Button, usePagination } from '@bloomneo/uikit'; const ALL_ITEMS = Array.from({ length: 234 }, (_, i) => `Item ${i + 1}`); export default function UsePaginationExample() { const pagination = usePagination({ total: ALL_ITEMS.length, pageSize: 10 }); const visible = ALL_ITEMS.slice(pagination.startIndex, pagination.endIndex); return (
    {visible.map((item) => (
  • {item}
  • ))}
{pagination.pages.map((p, idx) => p === 'ellipsis-start' || p === 'ellipsis-end' ? ( ) : ( ) )}

Page {pagination.page} of {pagination.pageCount} ·{' '} showing {pagination.startIndex + 1}–{pagination.endIndex} of {pagination.total}

); } ``` ## Cookbook — composed page patterns Whole-page recipes built from the primitives above. Start here when building a new feature instead of designing from scratch. ### Crud Page File: cookbook/crud-page.tsx ```tsx /** * CRUD page recipe. * * Searchable, sortable user list with row actions and a delete-with-confirm * flow. The whole thing is ~80 lines instead of the usual 600 because every * piece (PageHeader, DataTable, ConfirmProvider/useConfirm, ToastProvider/toast) * is a UIKit primitive. */ import { useState } from 'react'; import { Pencil, Trash2, Users } from 'lucide-react'; import { Button, ConfirmProvider, DataTable, PageHeader, ToastProvider, toast, useConfirm, type DataTableColumn, type RowAction, } from '@bloomneo/uikit'; type User = { id: string; name: string; email: string; role: 'admin' | 'user' }; const initialUsers: User[] = [ { id: '1', name: 'Alice', email: 'alice@example.com', role: 'admin' }, { id: '2', name: 'Bob', email: 'bob@example.com', role: 'user' }, { id: '3', name: 'Carol', email: 'carol@example.com', role: 'user' }, { id: '4', name: 'Dawud', email: 'dawud@example.com', role: 'user' }, ]; function UserListInner() { const [users, setUsers] = useState(initialUsers); const confirm = useConfirm(); const columns: DataTableColumn[] = [ { id: 'name', header: 'Name', accessorKey: 'name', sortable: true }, { id: 'email', header: 'Email', accessorKey: 'email' }, { id: 'role', header: 'Role', accessorKey: 'role', sortable: true }, ]; const actions: RowAction[] = [ { id: 'edit', label: 'Edit', icon: Pencil, onClick: (row) => toast(`Editing ${row.name}`), }, { id: 'delete', label: 'Delete', icon: Trash2, variant: 'destructive', onClick: async (row) => { const ok = await confirm({ title: `Delete ${row.name}?`, description: 'This cannot be undone.', confirmLabel: 'Delete', tone: 'destructive', }); if (!ok) return; setUsers((prev) => prev.filter((u) => u.id !== row.id)); toast.success(`${row.name} deleted`); }, }, ]; return (
} title="User management" description="View and manage all users in your workspace" breadcrumbs={[{ label: 'Admin', href: '/admin' }, { label: 'Users' }]} actions={} /> data={users} columns={columns} actions={actions} searchable pagination pageSize={10} getRowId={(row) => row.id} />
); } export default function CrudPageRecipe() { return ( ); } ``` ### Dashboard File: cookbook/dashboard.tsx ```tsx /** * Dashboard recipe. * * Stats grid + recent activity table inside a standard page shell. * Drop into any admin layout — works inside or alone. */ import { Activity, DollarSign, Package, Users } from 'lucide-react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, DataTable, PageHeader, formatCurrency, type DataTableColumn, } from '@bloomneo/uikit'; type Stat = { label: string; value: string; delta: string; icon: React.ReactNode }; type Order = { id: string; customer: string; amount: number; status: 'paid' | 'pending' }; const stats: Stat[] = [ { label: 'Revenue', value: formatCurrency(48230), delta: '+12.4%', icon: }, { label: 'Users', value: '1,284', delta: '+3.1%', icon: }, { label: 'Orders', value: '342', delta: '+8.0%', icon: }, { label: 'Sessions', value: '12,932', delta: '+22.0%', icon: }, ]; const orders: Order[] = [ { id: '#1023', customer: 'Alice', amount: 199.0, status: 'paid' }, { id: '#1024', customer: 'Bob', amount: 49.5, status: 'pending' }, { id: '#1025', customer: 'Carol', amount: 320.0, status: 'paid' }, ]; const orderColumns: DataTableColumn[] = [ { id: 'id', header: 'Order', accessorKey: 'id' }, { id: 'customer', header: 'Customer', accessorKey: 'customer' }, { id: 'amount', header: 'Amount', accessor: (row) => formatCurrency(row.amount), sortable: true, dataType: 'number', }, { id: 'status', header: 'Status', accessorKey: 'status' }, ]; export default function DashboardRecipe() { return (
{stats.map((s) => ( {s.label}
{s.icon}
{s.value}

{s.delta} vs last period

))}
Recent orders The latest activity across your store data={orders} columns={orderColumns} searchable={false} pagination={false} getRowId={(row) => row.id} />
); } ``` ### Delete Flow File: cookbook/delete-flow.tsx ```tsx /** * Destructive action recipe. * * Demonstrates the high-stakes deletion pattern: a button that opens a * confirm dialog where the user has to type the resource name before the * delete button enables. On success a toast confirms the action. * * Use this for irreversible operations: deleting users, dropping databases, * cancelling subscriptions, etc. */ import { Button, ConfirmProvider, ToastProvider, toast, useConfirm, } from '@bloomneo/uikit'; const RESOURCE_NAME = 'production-db'; function DeleteResource() { const confirm = useConfirm(); async function onDelete() { const ok = await confirm.destructive({ title: 'Delete production database', description: 'This will permanently destroy the database and all of its data. There is no undo.', verifyText: RESOURCE_NAME, confirmLabel: 'I understand, delete it', }); if (!ok) return; // → call your delete API here toast.success(`${RESOURCE_NAME} deleted`); } return ( ); } export default function DeleteFlowRecipe() { return (

The button below opens a confirmation dialog. The user must type {RESOURCE_NAME} before the delete button enables.

); } ``` ### Login File: cookbook/login.tsx ```tsx /** * Login recipe. * * Centered card with email + password form, inline validation, and a * submit button. No backend — wire your auth call where the comment is. */ import { useState } from 'react'; import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, FormField, Input, PasswordInput, ToastProvider, toast, } from '@bloomneo/uikit'; export default function LoginRecipe() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [submitting, setSubmitting] = useState(false); const emailError = email && !email.includes('@') ? 'Enter a valid email address' : undefined; const passwordError = password && password.length < 8 ? 'Must be at least 8 characters' : undefined; const canSubmit = email && password && !emailError && !passwordError && !submitting; async function onSubmit(e: React.FormEvent) { e.preventDefault(); setSubmitting(true); try { // → call your auth API here await new Promise((r) => setTimeout(r, 600)); toast.success('Welcome back'); } catch { toast.error('Login failed'); } finally { setSubmitting(false); } } return ( <>
Sign in Welcome back to your workspace
setEmail(e.target.value)} autoComplete="email" /> setPassword(e.target.value)} autoComplete="current-password" />
); } ``` ### Settings File: cookbook/settings.tsx ```tsx /** * Settings recipe. * * Tabs containing a profile form, a security form, and a notifications form. * Each section has its own save handler that fires a toast. */ import { useState } from 'react'; import { Button, FormField, Input, PageHeader, PasswordInput, Switch, Tabs, TabsContent, TabsList, TabsTrigger, ToastProvider, toast, } from '@bloomneo/uikit'; function ProfileForm() { const [name, setName] = useState('Alice'); const [email, setEmail] = useState('alice@example.com'); return (
{ e.preventDefault(); toast.success('Profile saved'); }} > setName(e.target.value)} /> setEmail(e.target.value)} />
); } function SecurityForm() { const [current, setCurrent] = useState(''); const [next, setNext] = useState(''); return (
{ e.preventDefault(); toast.success('Password updated'); }} > setCurrent(e.target.value)} /> setNext(e.target.value)} />
); } function NotificationsForm() { const [email, setEmail] = useState(true); const [push, setPush] = useState(false); return (
); } export default function SettingsRecipe() { return ( <>
Profile Security Notifications
); } ``` ## Conventions - Always import from `@bloomneo/uikit` (single canonical entry). - Pass `data` as an array (use `[]` while loading, never `undefined`). - Every `` column needs a unique `id`. - Mount `` and `` once at the app root. - Use `useConfirm()` for delete flows — never manage open/close state by hand. - Use the `format*` helpers for currency / dates / bytes — never inline `${val}`. - Use `useBreakpoint("md")` to react to viewport changes — do not write resize listeners. ## Full export list Every named export available from `@bloomneo/uikit`: - Accordion - AccordionContent - AccordionItem - AccordionTrigger - AdminLayout - Alert - AlertDescription - AlertTitle - ApiOptions - ApiResponse - AuthLayout - Avatar - AvatarFallback - AvatarImage - BREAKPOINTS - Badge - BlankLayout - Breadcrumb - BreadcrumbItem - BreadcrumbLink - BreadcrumbList - BreadcrumbPage - BreadcrumbSeparator - Breakpoint - BreakpointDirection - Button - Calendar - Card - CardContent - CardDescription - CardFooter - CardHeader - CardTitle - Checkbox - Collapsible - CollapsibleContent - CollapsibleTrigger - Combobox - ComboboxOption - ComboboxProps - Command - CommandDialog - CommandEmpty - CommandGroup - CommandInput - CommandItem - CommandList - CommandSeparator - CommandShortcut - ConfirmDialog - ConfirmDialogProps - ConfirmOptions - ConfirmProvider - Container - DataTable - DataTableCellValue - DataTableColumn - DataTableFilterValue - DataTableProps - DateInput - DestructiveConfirmOptions - Dialog - DialogContent - DialogDescription - DialogFooter - DialogHeader - DialogTitle - DialogTrigger - DropdownMenu - DropdownMenuCheckboxItem - DropdownMenuContent - DropdownMenuGroup - DropdownMenuItem - DropdownMenuLabel - DropdownMenuPortal - DropdownMenuRadioGroup - DropdownMenuRadioItem - DropdownMenuSeparator - DropdownMenuShortcut - DropdownMenuSub - DropdownMenuSubContent - DropdownMenuSubTrigger - DropdownMenuTrigger - EmptyState - EmptyStateProps - FilterConfig - FilterOperator - Footer - Form - FormControl - FormController - FormDescription - FormField - FormFieldProps - FormItem - FormLabel - FormMessage - FormatBytesOptions - FormatCurrencyOptions - FormatDateOptions - FormatNumberOptions - FoucScriptOptions - Header - HeaderLogo - HeaderNav - HoverCard - HoverCardContent - HoverCardTrigger - Input - Label - LayoutWrapper - Menubar - MenubarCheckboxItem - MenubarContent - MenubarItem - MenubarLabel - MenubarMenu - MenubarRadioGroup - MenubarRadioItem - MenubarSeparator - MenubarShortcut - MenubarSub - MenubarSubContent - MenubarSubTrigger - MenubarTrigger - MobileLayout - Mode - Nullable - PageHeader - PageHeaderCrumb - PageHeaderProps - PageLayout - Pagination - PaginationContent - PaginationEllipsis - PaginationItem - PaginationLink - PaginationNext - PaginationPage - PaginationPrevious - PasswordInput - PasswordInputProps - PermissionCheck - PermissionContextValue - PermissionGate - PermissionGateProps - PermissionProvider - PermissionProviderProps - PermissionWhen - Popover - PopoverContent - PopoverTrigger - PopupLayout - Progress - RadioGroup - RadioGroupItem - RowAction - SafeArea - Select - SelectContent - SelectGroup - SelectItem - SelectLabel - SelectTrigger - SelectValue - Separator - Sheet - SheetClose - SheetContent - SheetDescription - SheetFooter - SheetHeader - SheetTitle - SheetTrigger - Skeleton - Slider - SortConfig - Switch - TabBar - Table - TableBody - TableCaption - TableCell - TableHead - TableHeader - TableRow - Tabs - TabsContent - TabsList - TabsTrigger - Textarea - Theme - ThemeProvider - Time - TimeAgoOptions - TimeProps - ToastAction - ToastOptions - ToastPosition - ToastProvider - ToastProviderProps - Toaster - Toggle - Tooltip - TooltipContent - TooltipProvider - TooltipTrigger - UIKitError - UseApiReturn - UseConfirmReturn - UseDataTableOptions - UseDataTableReturn - UseLocalStorageReturn - UsePaginationOptions - UsePaginationReturn - breakpointQuery - cn - formatBytes - formatCurrency - formatDate - formatNumber - foucScript - foucScriptTag - requireArrayProp - requireProp - timeAgo - toast - useActiveBreakpoint - useApi - useBackendStatus - useBreakpoint - useConfirm - useDataTable - useLocalStorage - useMediaQuery - useMobileLayout - usePagination - usePermission - useTheme - useToast - warnInDev ## Where to look next - Type definitions: `dist/types/index.d.ts` (full prop shapes) - Source: https://github.com/bloomneo/uikit - Issues: https://github.com/bloomneo/uikit/issues