npm

Reference

Components

Every component imports from its own entry point (@viliha/vui-ui/<name>), so you ship only what you use. Live examples below.

Button

Seven variants and four sizes; the primary variant carries the brand blue.

import { Button } from "@viliha/vui-ui/button";

<Button>Default</Button>
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
<Button variant="destructive">Destructive</Button>

Badge

DefaultSecondaryOutlineMutedSuccessWarningDestructive
import { Badge } from "@viliha/vui-ui/badge";

<Badge>Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="outline">Outline</Badge>
<Badge variant="muted">Muted</Badge>
<Badge variant="success">Success</Badge>
<Badge variant="warning">Warning</Badge>
<Badge variant="destructive">Destructive</Badge>

Card

Team

12 members across 3 regions.
import { Card, CardContent, CardHeader, CardTitle } from "@viliha/vui-ui/card";

<Card>
  <CardHeader><CardTitle>Team</CardTitle></CardHeader>
  <CardContent>12 members across 3 regions.</CardContent>
</Card>

Input

import { Input } from "@viliha/vui-ui/input";

<Input placeholder="you@example.com" />

Checkbox

import { Checkbox } from "@viliha/vui-ui/checkbox";

<Checkbox checked={checked} onChange={(e) => setChecked(e.target.checked)} />

Select

import { Select } from "@viliha/vui-ui/select";

<Select
  value={value}
  onValueChange={setValue}
  options={[
    { value: "apple", label: "Apple" },
    { value: "banana", label: "Banana" },
  ]}
/>

Combobox

A searchable single-select. Same API as Select, but the popover leads with a type-to-filter input. Use it for long option lists (an FK / country picker) where scrolling a Select is painful.

import { Combobox } from "@viliha/vui-ui/combobox";

<Combobox
  value={value}
  onValueChange={setValue}
  options={countries}          // [{ value, label }, …]
  placeholder="Select country…"
  searchPlaceholder="Search countries…"
/>

Cascading combobox

One searchable Combobox per fixed, named level(Region → Country → State → City). Picking a level narrows the next from the selected node's children and clears everything downstream; a level stays disabled until its parent is chosen. Depth comes from the data: pass a 3-, 4-, or N-level tree.

import { CascadingCombobox } from "@viliha/vui-ui/cascading-combobox";

<CascadingCombobox
  levels={[
    { key: "region", label: "Region" },
    { key: "country", label: "Country" },
    { key: "state", label: "State" },
    { key: "city", label: "City" },
  ]}
  items={LOCATIONS}          // tree: node.children feeds the next level
  value={path}               // string[], one value per level
  onValueChange={(next) => setPath(next)}
  orientation="horizontal"
/>

Toast

Global notifications for errors and events. Mount <Toaster /> once in your root layout, then call toast(...) from anywhere (event handlers, catch blocks), no provider needed. Supports a title, description, an action button (e.g. Undo), and success/error/warning variants.

import { toast, Toaster } from "@viliha/vui-ui/toast";

// once, in the root layout:
<Toaster />

// anywhere:
toast({
  title: "Event created",
  description: "Sunday, December 3 at 9:00 AM",
  action: { label: "Undo", onClick: restore },
});
toast.success("Saved");
toast.error("Couldn't save", { description: "Try again." });

Tooltip

A themed, dependency-free tooltip matching the shadcn design (dark bubble + arrow), portal-positioned with hover/focus. side defaults to "auto", it prefers bottom and flips to toponly when there isn't room below (e.g. the last rows of a table). RecordView uses it for truncated cells; reach for it instead of the native title attribute.

Hover me
import { Tooltip } from "@viliha/vui-ui/tooltip";

<Tooltip content="Full text shown on hover" side="auto">
  <span className="underline decoration-dotted">Hover me</span>
</Tooltip>

Avatar

SB
AU
import { Avatar, AvatarFallback } from "@viliha/vui-ui/avatar";

<Avatar><AvatarFallback>SB</AvatarFallback></Avatar>
import { Dropdown, DropdownItem, DropdownLabel } from "@viliha/vui-ui/dropdown-menu";

<Dropdown label="Actions">
  <DropdownLabel>Manage</DropdownLabel>
  <DropdownItem onSelect={() => {}}>Edit</DropdownItem>
  <DropdownItem onSelect={() => {}}>Duplicate</DropdownItem>
</Dropdown>

RecordView

The full data table: editable cells, auto-sizing columns, a sticky header, sort/filter/pagination, row actions, a buffered add/edit form, and CSV/JSON/Excel/PDF import & export. Configure it with a fields array and mock or real data, then see it live in the backoffice demo.

organizations-table.tsx
import { RecordView, type RecordField } from "@viliha/vui-ui/record-view";

const fields: RecordField<Org>[] = [
  { key: "name", label: "Name", editable: true, required: true, hideInTable: true },
  { key: "domain", label: "Domain", editable: true, copyable: true },
  { key: "country", label: "Country", editable: true },
];

<RecordView
  title="Organizations"
  singular="Organization"
  fields={fields}
  initialData={data}
  makeEmptyRow={() => ({ id: Date.now(), name: "", domain: "", country: "" })}
  getPrimary={(row) => ({ title: row.name, initials: row.name.slice(0, 2) })}
/>

Table

Low-level, unstyled-to-theme table primitives (Table, TableHeader, TableBody, TableRow, TableHead, TableCell, plus TableFooter / TableCaption). Use these for a plain static table; for a data grid with sorting, filtering, paging and editing, reach for RecordView instead.

NameRole
Ada LovelaceAdmin
Alan TuringMember
import {
  Table, TableHeader, TableBody, TableRow, TableHead, TableCell,
} from "@viliha/vui-ui/table";

<Table>
  <TableHeader>
    <TableRow><TableHead>Name</TableHead><TableHead>Role</TableHead></TableRow>
  </TableHeader>
  <TableBody>
    <TableRow><TableCell>Ada</TableCell><TableCell>Admin</TableCell></TableRow>
  </TableBody>
</Table>

Kbd & Shortcut

Keyboard key caps. Kbd renders a single cap; Shortcut renders a combo from a keys array, joined with +, used in menus and the ⌘K launchers.

EscKK
import { Kbd, Shortcut } from "@viliha/vui-ui/kbd";

<Kbd>Esc</Kbd>
<Shortcut keys={["⌘", "K"]} />

Required mark

The mandatory-field asterisk used across forms, filters and auth: one consistent cue. Place it next to a label; it takes no props.

Email
import { RequiredMark } from "@viliha/vui-ui/required-mark";

<label className="flex items-center gap-1">Email <RequiredMark /></label>

Code

A minimal inline <code> element for referencing a token, path or value in running text (as opposed to CodeBlock, which is for multi-line snippets).

Run pnpm dev to start.
import { Code } from "@viliha/vui-ui/code";

Run <Code>pnpm dev</Code> to start.