Design system
for web apps and websites
Unified design tokens and React components. One import. Pre-compiled CSS. Built for consistency and speed.
@gusvega/ui
Components, built-in theme values, and compiled CSS in one package.
Theme overrides
Customize colors, spacing, and type with CSS variables or createThemeVariables().
Get Started
Start building in minutes
Install the packages you need. Import CSS. Start building.
Everything in One Package
npm install @gusvega/uiComponents, compiled CSS, and default theme values ship together.
Simple Theme Overrides
import { createThemeVariables } from '@gusvega/ui'Override theme values globally or for one subtree without another package.
// layout.tsx
import "@gusvega/ui/dist/style.css";
// any page/component
import { Button } from "@gusvega/ui";
Design System
Built-In Theme
Colors
Customize colors with CSS variables or the theme helper:
import { createThemeVariables } from '@gusvega/ui';
const themeVars = createThemeVariables({
colors: {
neutral: {
900: '#0f172a',
},
},
});neutral-100
neutral-500
neutral-900
Spacing
Override spacing in one place and every component picks it up:
:root {
--gus-space-4: 1.125rem;
--gus-space-6: 1.75rem;
}Typography
Typography stays overridable without rebuilding the library:
:root {
--gus-font-family-sans: "Satoshi", ui-sans-serif, system-ui, sans-serif;
--gus-font-size-base: 1rem;
--gus-line-height-base: 1.6rem;
}Font Sizes
xs: 12px
sm: 14px
base: 16px
lg: 18px
2xl: 24px
Font Weights
normal: 400
medium: 500
semibold: 600
bold: 700
Extend & Customize
1. Import the library styles once:
// app/layout.tsx import '@gusvega/ui/style.css';
2. Override values in CSS or generate them in React:
import { createThemeVariables } from '@gusvega/ui';
const themeVars = createThemeVariables({
colors: {
neutral: {
900: '#0f172a',
100: '#f1f5f9',
},
},
});3. Apply the variables and the components update automatically:
<div style={themeVars}>
<Card>
<Button>Click me</Button>
</Card>
</div>Showcase
Components
Badge
Data DisplaySmall status label. 3 variants for different levels of emphasis.
import { Badge } from '@gusvega/ui';
<Badge variant="default">Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="outline">Outline</Badge>
// In context
<Badge variant="secondary">New</Badge>
<Badge variant="outline">v0.0.1</Badge>
<Badge>MIT</Badge>
<Badge variant="secondary">TypeScript</Badge>Avatar
Data DisplayUser representation with initials or image. 3 sizes, 3 colors, stackable.
import { Avatar } from '@gusvega/ui';
// Sizes
<Avatar initials="SM" size="sm" />
<Avatar initials="MD" size="md" />
<Avatar initials="LG" size="lg" />
// Colors
<Avatar initials="Dk" color="dark" />
<Avatar initials="Md" color="medium" />
<Avatar initials="Lt" color="light" />
// Stacked group
<div className="flex -space-x-2">
<Avatar initials="A" size="sm" color="dark" />
<Avatar initials="B" size="sm" color="medium" />
<Avatar initials="+3" size="sm" color="light" />
</div>Input
FormText input with forwardRef support, error state, and disabled variant.
import { Input } from '@gusvega/ui';
<Input placeholder="Default input" />
// Error state
<Input
placeholder="With error state"
error="This field is required"
/>
// Disabled
<Input placeholder="Disabled" disabled />Label
FormForm field label with optional required marker.
import { Label } from '@gusvega/ui';
<Label htmlFor="email">Standard label</Label>
<Label htmlFor="name" required>Required label</Label>
// With input
<Label htmlFor="input" className="mb-1.5">With input</Label>
<Input id="input" placeholder="Value..." />Textarea
FormMultiline text input with forwardRef support and resizable height.
import { Textarea } from '@gusvega/ui';
<Textarea placeholder="Write your message here..." rows={3} />
// Disabled
<Textarea placeholder="Disabled textarea" disabled rows={2} />Checkbox
FormControlled boolean input with peer-based custom styling.
import { Checkbox } from '@gusvega/ui';
const [v, setV] = useState({
email: true,
marketing: false,
auth: true,
});
<Checkbox
checked={v.email}
onChange={(x) => setV(p => ({ ...p, email: x }))}
label="Email notifications"
/>
<Checkbox
checked={v.marketing}
onChange={(x) => setV(p => ({ ...p, marketing: x }))}
label="Marketing emails"
/>
<Checkbox
checked={v.auth}
onChange={(x) => setV(p => ({ ...p, auth: x }))}
label="Two-factor auth"
/>
<Checkbox
checked={false}
onChange={() => {}}
label="Disabled option"
disabled
/>Radio
FormSingle-select controlled input. Group via a shared name prop.
import { Radio } from '@gusvega/ui';
const [plan, setPlan] = useState('pro');
<Radio name="plan" value="free"
label="Free — $0/mo"
checked={plan === 'free'} onChange={setPlan} />
<Radio name="plan" value="pro"
label="Pro — $12/mo"
checked={plan === 'pro'} onChange={setPlan} />
<Radio name="plan" value="team"
label="Team — $49/mo"
checked={plan === 'team'} onChange={setPlan} />
<Radio name="plan" value="enterprise"
label="Enterprise (contact us)"
checked={plan === 'enterprise'} onChange={setPlan}
disabled />Switch
FormToggle control for binary settings. Animates on state change.
import { Switch } from '@gusvega/ui';
const [v, setV] = useState({
notifs: true,
marketing: false,
updates: true,
});
<Switch
checked={v.notifs}
onChange={(x) => setV(p => ({ ...p, notifs: x }))}
label="Push notifications"
/>
<Switch
checked={v.marketing}
onChange={(x) => setV(p => ({ ...p, marketing: x }))}
label="Marketing emails"
/>
<Switch
checked={v.updates}
onChange={(x) => setV(p => ({ ...p, updates: x }))}
label="Product updates"
/>
<Switch checked={false} onChange={() => {}}
label="Disabled" disabled />Select
FormStyled native select with custom chevron. Supports placeholder and disabled options.
import { Select } from '@gusvega/ui';
<Select
placeholder="Choose a framework..."
options={[
{ value: 'next', label: 'Next.js' },
{ value: 'remix', label: 'Remix' },
{ value: 'vite', label: 'Vite' },
{ value: 'astro', label: 'Astro', disabled: true },
]}
onChange={(value) => console.log(value)}
/>FormField
FormCompound wrapper that connects Label, input, error message, and hint text.
Only letters and numbers.
Invalid email address.
import { FormField, Input } from '@gusvega/ui';
// With hint
<FormField
label="Username"
htmlFor="ff-1"
hint="Only letters and numbers."
>
<Input id="ff-1" placeholder="gus_vega" />
</FormField>
// With error
<FormField
label="Email"
htmlFor="ff-2"
required
error="Invalid email address."
>
<Input id="ff-2" type="email" placeholder="you@example.com" />
</FormField>Tag
Data DisplayRectangular label with optional remove button. Use for categories, filters, or selections.
import { Tag } from '@gusvega/ui';
<Tag>Default</Tag>
<Tag variant="default">Dark</Tag>
<Tag variant="outline">Outline</Tag>
// Removable
<Tag onRemove={() => {}}>TypeScript</Tag>
<Tag onRemove={() => {}}>React</Tag>
<Tag onRemove={() => {}}>Tailwind</Tag>Stat
Data DisplayMetric display with optional trend indicator. Use in dashboards and overview cards.
Total Users
12,345
↑+8.2% this month
Revenue
$84.2k
↑+12.5% this month
Churn Rate
2.4%
↓-0.3% this month
Uptime
99.9%
import { Stat } from '@gusvega/ui';
<Stat label="Total Users" value="12,345"
change="+8.2% this month" trend="up" />
<Stat label="Revenue" value="$84.2k"
change="+12.5% this month" trend="up" />
<Stat label="Churn Rate" value="2.4%"
change="-0.3% this month" trend="down" />
<Stat label="Uptime" value="99.9%" />Table
Data DisplayStructured data display with hover rows and responsive overflow scroll.
| Name | Role | Status | Joined |
|---|---|---|---|
| Gus Vega | Engineer | Active | Jan 2024 |
| Jane Doe | Designer | Active | Mar 2024 |
| Alex Kim | PM | Away | Jun 2024 |
import {
Table, TableHeader, TableBody,
TableRow, TableHead, TableCell,
Badge
} from '@gusvega/ui';
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Role</TableHead>
<TableHead>Status</TableHead>
<TableHead>Joined</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">Gus Vega</TableCell>
<TableCell>Engineer</TableCell>
<TableCell><Badge>Active</Badge></TableCell>
<TableCell>Jan 2024</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Jane Doe</TableCell>
<TableCell>Designer</TableCell>
<TableCell><Badge>Active</Badge></TableCell>
<TableCell>Mar 2024</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Alex Kim</TableCell>
<TableCell>PM</TableCell>
<TableCell><Badge variant="secondary">Away</Badge></TableCell>
<TableCell>Jun 2024</TableCell>
</TableRow>
</TableBody>
</Table>Code & Kbd
Data DisplayInline code and block code for documentation. Kbd for keyboard shortcut hints.
Run npm install gus-ui to get started.
import { Code, Kbd } from '@gusvega/ui';
// Inline
<p>Run <Code>npm install gus-ui</Code> to get started.</p>
// Block
<Code block>
{"import { Button } from '@gusvega/ui';"}
</Code>
// Keyboard shortcuts
<div className="flex items-center gap-1.5">
<Kbd>⌘</Kbd>
<Kbd>K</Kbd>
<span className="text-xs text-neutral-500">Command palette</span>
</div>
<div className="flex items-center gap-1.5">
<Kbd>⌘</Kbd>
<Kbd>S</Kbd>
<span className="text-xs text-neutral-500">Save</span>
</div>Progress
FeedbackAnimated fill bar for indicating completion. Value range: 0–100.
import { Progress } from '@gusvega/ui';
<Progress value={72} />
// With labels
<div>
<div className="flex justify-between mb-2">
<span>Storage</span>
<span>72%</span>
</div>
<Progress value={72} />
</div>
<div>
<div className="flex justify-between mb-2">
<span>Bandwidth</span>
<span>45%</span>
</div>
<Progress value={45} />
</div>
<div>
<div className="flex justify-between mb-2">
<span>Memory</span>
<span>91%</span>
</div>
<Progress value={91} />
</div>Spinner
FeedbackCSS animation loading indicator. 3 sizes for different contexts.
import { Spinner } from '@gusvega/ui';
<Spinner size="sm" />
<Spinner size="md" />
<Spinner size="lg" />
// With button
<Button disabled>
<Spinner size="sm" className="mr-2" />
Loading...
</Button>Alert
FeedbackContextual message with optional title. 3 variants for different emphasis levels.
Heads up
Your free trial ends in 3 days. Upgrade to keep access.
Action required
Please verify your email address to continue.
Deployed successfully
Version 1.2.0 is now live in production.
import { Alert } from '@gusvega/ui';
<Alert variant="default" title="Heads up">
Your free trial ends in 3 days. Upgrade to keep access.
</Alert>
<Alert variant="outline" title="Action required">
Please verify your email address to continue.
</Alert>
<Alert variant="filled" title="Deployed successfully">
Version 1.2.0 is now live in production.
</Alert>Skeleton
FeedbackPulse animation placeholder for loading states. Rectangular, circular, and text variants.
import { Skeleton } from '@gusvega/ui';
// Text lines
<Skeleton variant="text" />
<Skeleton variant="text" width="60%" />
// Circular (avatar placeholder)
<Skeleton variant="circular" width={40} height={40} />
// Rectangular (image / card placeholder)
<Skeleton variant="rectangular" width={200} height={120} />Tabs
NavigationPanel switcher with context-based state. Compound: Tabs, TabsList, TabsTrigger, TabsContent.
Tabs organize content into separate panels — only one panel is visible at a time.
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@gusvega/ui';
<Tabs defaultValue="overview">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="code">Code</TabsTrigger>
<TabsTrigger value="api">API</TabsTrigger>
<TabsTrigger value="disabled" disabled>Disabled</TabsTrigger>
</TabsList>
<TabsContent value="overview">Overview content here.</TabsContent>
<TabsContent value="code">Code content here.</TabsContent>
<TabsContent value="api">API content here.</TabsContent>
</Tabs>Link
NavigationStyled anchor element. 3 variants: default (underline), muted, and decorated underline.
Read the documentation or view the source on GitHub.
import { Link } from '@gusvega/ui';
<Link href="#">Default link</Link>
<Link href="#" variant="muted">Muted link</Link>
<Link href="#" variant="underline">Underline link</Link>
// In prose
<p>
Read the <Link href="#">documentation</Link> or view the{' '}
<Link href="#" variant="muted">source on GitHub</Link>.
</p>Card
LayoutContent container with header, body, and footer slots. All sub-components are optional.
Gus Vega
Design Systems Engineer
Building the GUS UI design system — tokens, components, and documentation.
import {
Card, CardHeader, CardContent, CardFooter,
Avatar, Badge, Button
} from '@gusvega/ui';
<Card className="w-full">
<CardHeader>
<div className="flex items-center gap-3">
<Avatar initials="GU" size="md" />
<div>
<p className="font-semibold">Gus Vega</p>
<p className="text-xs text-neutral-500">Design Systems Engineer</p>
</div>
</div>
</CardHeader>
<CardContent>
<p className="text-sm text-neutral-500 leading-relaxed">
Building the GUS UI design system — tokens, components, and documentation.
</p>
<div className="flex gap-2 mt-3">
<Badge variant="secondary">Design</Badge>
<Badge variant="outline">TypeScript</Badge>
<Badge variant="outline">React</Badge>
</div>
</CardContent>
<CardFooter className="flex justify-end gap-2">
<Button variant="ghost" size="sm">Message</Button>
<Button size="sm">Follow</Button>
</CardFooter>
</Card>Separator
LayoutVisual divider for horizontal and vertical layouts.
Above the separator
Below the separator
import { Separator } from '@gusvega/ui';
// Horizontal (default)
<Separator />
// Vertical (set a fixed height on parent)
<div className="flex items-center gap-4 h-8">
<span>Tokens</span>
<Separator orientation="vertical" />
<span>Library</span>
<Separator orientation="vertical" />
<span>Refapp</span>
</div>Stack
LayoutFlex layout helper with direction, gap, alignment, and justify props.
import { Stack, Badge, Button } from '@gusvega/ui';
// Row
<Stack direction="row" gap={2} align="center">
<Badge>Row</Badge>
<Badge variant="secondary">Item 2</Badge>
<Badge variant="outline">Item 3</Badge>
</Stack>
// Column
<Stack direction="col" gap={2}>
<Button size="sm">First</Button>
<Button size="sm" variant="secondary">Second</Button>
<Button size="sm" variant="ghost">Third</Button>
</Stack>
// Space between
<Stack direction="row" justify="between" align="center"
className="border border-neutral-100 rounded-lg p-3">
<span className="font-medium">Dashboard</span>
<div className="flex gap-2">
<Button size="sm" variant="ghost">Cancel</Button>
<Button size="sm">Save</Button>
</div>
</Stack>Container
LayoutMax-width wrapper for consistent page and section layouts. Supports polymorphic 'as' prop.
Container example
Max-width wrapper with consistent padding and alignment
import { Container } from '@gusvega/ui';
// Default (div)
<Container>
<h1>Page content</h1>
</Container>
// As section
<Container as="section">
<h2>Section title</h2>
</Container>
// With custom className
<Container className="py-12">
Content with padding
</Container>PageIntro
LayoutHero section for page headers with optional eyebrow, title, and children content.
Welcome - Build Amazing Interfaces
With a composable component library.
import { PageIntro } from '@gusvega/ui';
<PageIntro
eyebrow="Welcome"
title="Build Amazing Interfaces"
>
<p>With a composable component library.</p>
</PageIntro>
// Centered variant
<PageIntro
eyebrow="Features"
title="What's Included"
centered
>
<Stack direction="row" gap={4}>
<div>Feature 1</div>
<div>Feature 2</div>
</Stack>
</PageIntro>SectionIntro
LayoutSection header with eyebrow and title. Supports smaller and invert variants.
Our Team - Meet the experts
Process - How we work
import { SectionIntro } from '@gusvega/ui';
// Standard
<SectionIntro
eyebrow="Our Team"
title="Meet the experts"
/>
// Smaller variant
<SectionIntro
eyebrow="Process"
title="How we work"
smaller
/>List
LayoutSemantic list component with ListItem compound. Supports animations with FadeIn.
- Beautiful design system. Consistent components and tokens across projects
- Type-safe components. Full TypeScript support with proper type inference
- CSS compiled. Pre-compiled CSS bundle with Tailwind CSS
import { List, ListItem } from '@gusvega/ui';
<List>
<ListItem title="Beautiful design system">
Consistent components and tokens across projects
</ListItem>
<ListItem title="Type-safe components">
Full TypeScript support with proper type inference
</ListItem>
<ListItem title="CSS compiled">
Pre-compiled CSS bundle with Tailwind CSS
</ListItem>
</List>Blockquote
LayoutSemantic blockquote component with optional author and cite attributes.
Great design systems save time and improve consistency.
Components should be composable and flexible.
import { Blockquote } from '@gusvega/ui';
<Blockquote author={{ name: 'Design Team', role: 'Gus UI' }}>
Great design systems save time and improve consistency.
</Blockquote>
// With different author
<Blockquote author={{ name: 'Contributors', role: 'Open Source' }}>
Components should be composable and flexible.
</Blockquote>Modal
OverlaysBlocking dialog with backdrop, title, content, and action footer.
import { Modal, Button } from '@gusvega/ui';
import { useState } from 'react';
const [open, setOpen] = useState(false);
<Button onClick={() => setOpen(true)}>Open Modal</Button>
<Modal
open={open}
onClose={() => setOpen(false)}
title="Confirm Action"
description="Are you sure?"
footer={
<>
<Button variant="ghost" size="sm" onClick={() => setOpen(false)}>Cancel</Button>
<Button size="sm">Confirm</Button>
</>
}
>
<p>This action cannot be undone.</p>
</Modal>Drawer
OverlaysSide panel overlay for navigation or content. Left or right positioning.
import { Drawer, Button } from '@gusvega/ui';
import { useState } from 'react';
const [open, setOpen] = useState(false);
<Button onClick={() => setOpen(true)}>Open Drawer</Button>
<Drawer open={open} onClose={() => setOpen(false)} title="Navigation">
<nav className="space-y-2">
<a href="#" className="block text-sm hover:text-neutral-900">Home</a>
<a href="#" className="block text-sm hover:text-neutral-900">Components</a>
<a href="#" className="block text-sm hover:text-neutral-900">Docs</a>
</nav>
</Drawer>Tooltip
OverlaysLightweight hint displayed on hover. Positioned around trigger.
import { Tooltip, Button } from '@gusvega/ui';
<Tooltip text="Click to save" side="top">
<Button>Save</Button>
</Tooltip>
<Tooltip text="Delete this item" side="bottom">
<Button variant="ghost">Delete</Button>
</Tooltip>Popover
OverlaysFloating panel triggered by click/hover. Positioned relative to trigger.
import { Popover, Button } from '@gusvega/ui';
<Popover trigger={<Button>Options</Button>} side="bottom">
<div className="space-y-2">
<button className="block text-sm hover:text-neutral-900">Edit</button>
<button className="block text-sm hover:text-neutral-900">Share</button>
<button className="block text-sm hover:text-neutral-900">Delete</button>
</div>
</Popover>Accordion
Data DisplayCollapsible content sections. Click to expand/collapse.
import { Accordion, AccordionItem } from '@gusvega/ui';
<Accordion>
<AccordionItem title="What is Gus UI?">
A design system for React with tokens and pre-built components.
</AccordionItem>
<AccordionItem title="How do I use it?">
Import components and styles, then build your UI.
</AccordionItem>
<AccordionItem title="Is it free?">
Yes, Gus UI is open source and free to use.
</AccordionItem>
</Accordion>Stepper
FeedbackMulti-step form indicator. Shows progress through steps.
Personal
Billing
Review
import { Stepper } from '@gusvega/ui';
<Stepper
steps={['Personal Info', 'Billing', 'Confirm']}
currentStep={1}
/>Pagination
NavigationNavigate through paginated data. Smart page number display.
import { Pagination } from '@gusvega/ui';
const [page, setPage] = useState(1);
<Pagination
currentPage={page}
totalPages={10}
onPageChange={setPage}
/>Timeline
Data DisplayDisplay chronological events. Vertical timeline with dates.
v1.0.0 Released
Jan 15, 2024
v1.1.0 Released
Added new components
Feb 10, 2024
v1.2.0 Released
Mar 20, 2024
import { Timeline } from '@gusvega/ui';
<Timeline events={[
{ title: 'v1.0.0 Released', date: 'Jan 15, 2024' },
{ title: 'v1.1.0 Released', date: 'Feb 10, 2024', description: 'Added new components' },
{ title: 'v1.2.0 Released', date: 'Mar 20, 2024' },
]} />Rating
FormStar rating input. 1-5 stars for feedback.
Interactive
Readonly
import { Rating } from '@gusvega/ui';
const [rate, setRate] = useState(4);
<Rating value={rate} onChange={setRate} />
// Readonly
<Rating value={5} readonly />ToggleGroup
FormButton group for single or multi-selection.
import { ToggleGroup } from '@gusvega/ui';
<ToggleGroup
options={[
{ value: 'left', label: 'Left' },
{ value: 'center', label: 'Center' },
{ value: 'right', label: 'Right' },
]}
onChange={(value) => console.log(value)}
/>Chip
Data DisplaySmall tag-like element. Removable with close button.
import { Chip } from '@gusvega/ui';
<Chip label="Design" variant="secondary" />
<Chip label="React" variant="outline" />
<Chip label="Remove me" onRemove={() => {}} />EmptyState
FeedbackMessage display for empty lists or no results state.
No results found
Try adjusting your search or filters
import { EmptyState, Button } from '@gusvega/ui';
<EmptyState
title="No results found"
description="Try adjusting your search or filters"
action={<Button size="sm">Clear filters</Button>}
/>HoverCard
OverlaysCard revealed on hover. For rich preview content.
import { HoverCard, Avatar } from '@gusvega/ui';
<HoverCard trigger={<Avatar initials="GV" size="md" />}>
<div>
<p className="font-semibold">Gus Vega</p>
<p className="text-xs text-neutral-500">Designer</p>
</div>
</HoverCard>Toast
FeedbackNon-blocking notification with optional type and duration.
Toasts appear at bottom-left of screen
import { Toast, Button } from '@gusvega/ui';
import { useState } from 'react';
const [showToast, setShowToast] = useState(false);
<Button onClick={() => setShowToast(true)}>Show Toast</Button>
{showToast && (
<Toast
message="Operation completed successfully!"
type="success"
onClose={() => setShowToast(false)}
/>
)}Combobox
FormSearchable dropdown with filtering and keyboard navigation.
import { Combobox } from '@gusvega/ui';
<Combobox
options={[
{ value: 'react', label: 'React' },
{ value: 'vue', label: 'Vue' },
{ value: 'svelte', label: 'Svelte' },
]}
placeholder="Search frameworks..."
onChange={(value) => console.log(value)}
/>MultiSelect
FormMulti-option select with checkboxes and removable tags.
import { MultiSelect } from '@gusvega/ui';
<MultiSelect
options={[
{ value: 'ts', label: 'TypeScript' },
{ value: 'react', label: 'React' },
{ value: 'tailwind', label: 'Tailwind' },
]}
onChange={(values) => console.log(values)}
/>Slider
FormRange input with min/max/step and value display.
import { Slider } from '@gusvega/ui';
import { useState } from 'react';
const [value, setValue] = useState(50);
<Slider
min={0}
max={100}
step={1}
value={value}
onChange={setValue}
/>DatePicker
FormDate input with label, hint, validation, and min/max constraints.
Select your travel date
Between 2024 – 2026
This field is required
import { DatePicker } from '@gusvega/ui';
// Basic
<DatePicker label="Start Date" hint="Select your travel date" />
// With constraints
<DatePicker label="Deadline" required min="2024-01-01" max="2026-12-31" />
// Error state
<DatePicker label="Expiry" error="This field is required" />TimePicker
FormTime input with label, hint, validation, min/max, and step interval.
Business hours only
15-minute intervals
Please select a valid time
import { TimePicker } from '@gusvega/ui';
// With range constraint
<TimePicker label="Meeting Time" min="09:00" max="18:00" hint="Business hours only" />
// 15-minute intervals
<TimePicker label="Reminder" step={900} hint="15-minute intervals" />
// Error state
<TimePicker label="Deadline" error="Please select a valid time" />FileUpload
FormDrag & drop file upload with per-file list, size display, progress slot, and validation.
Drag files here or click to select
.pdf,.doc,.docx,.png,.jpg up to 5.0 MB · max 5 files
import { FileUpload } from '@gusvega/ui';
<FileUpload
multiple
accept=".pdf,.doc,.docx,.png,.jpg"
maxSize={5 * 1024 * 1024}
maxFiles={5}
onFileSelect={(files) => console.log(files)}
/>SearchBox
FormSearch input with clear button.
import { SearchBox } from '@gusvega/ui';
import { useState } from 'react';
const [search, setSearch] = useState('');
<SearchBox value={search} onChange={setSearch} placeholder="Search..." />InputGroup
FormInput with optional prefix and suffix elements.
import { InputGroup } from '@gusvega/ui';
<InputGroup prefix="$" suffix=".00" placeholder="0" />FormGroup
FormFieldset wrapper with legend and description.
import { FormGroup, Input } from '@gusvega/ui';
<FormGroup legend="Preferences" description="Customize your settings">
<Input placeholder="Enter value" />
</FormGroup>Collapsible
NavigationExpandable/collapsible container with animated arrow.
import { Collapsible } from '@gusvega/ui';
<Collapsible title="Advanced Options">
<div>Hidden content here</div>
</Collapsible>SegmentedControl
ActionsGrouped button selection with active styling.
import { SegmentedControl } from '@gusvega/ui';
import { useState } from 'react';
const [selected, setSelected] = useState('day');
<SegmentedControl
options={[
{ value: 'day', label: 'Day' },
{ value: 'week', label: 'Week' },
{ value: 'month', label: 'Month' },
]}
value={selected}
onChange={setSelected}
/>Tree
Data DisplayHierarchical data display with expand/collapse.
import { Tree } from '@gusvega/ui';
const data = {
label: 'Root',
children: [
{ label: 'Child 1' },
{ label: 'Child 2', children: [{ label: 'Grandchild' }] },
],
};
<Tree node={data} />DataGrid
Data DisplayTable with sortable columns and hover effects.
| John Doe | john@example.com |
| Jane Smith | jane@example.com |
import { DataGrid } from '@gusvega/ui';
<DataGrid
columns={[
{ key: 'name', label: 'Name' },
{ key: 'email', label: 'Email' },
]}
rows={[
{ name: 'John', email: 'john@example.com' },
]}
/>Carousel
Data DisplayContent carousel with navigation and dot indicators.
import { Carousel } from '@gusvega/ui';
<Carousel>
<div className="bg-blue-100 h-32 flex items-center justify-center">Slide 1</div>
<div className="bg-green-100 h-32 flex items-center justify-center">Slide 2</div>
<div className="bg-red-100 h-32 flex items-center justify-center">Slide 3</div>
</Carousel>BadgeCounter
Data DisplayNumber badge with 99+ display format.
import { BadgeCounter } from '@gusvega/ui';
<div className="flex gap-4">
<BadgeCounter count={5} />
<BadgeCounter count={99} />
<BadgeCounter count={150} />
</div>AlertDialog
OverlaysModal confirmation dialog with title and description.
import { AlertDialog, Button } from '@gusvega/ui';
import { useState } from 'react';
const [open, setOpen] = useState(false);
<Button size="sm" onClick={() => setOpen(true)}>Open Dialog</Button>
{open && (
<AlertDialog
title="Confirm Action"
description="Are you sure? This cannot be undone."
onConfirm={() => setOpen(false)}
onCancel={() => setOpen(false)}
/>
)}DropZone
OverlaysKeyboard-accessible drop zone with file list, size display, per-file removal, and size validation.
Drop images here
or click to browse — image/* — max 2.0 MB
import { DropZone } from '@gusvega/ui';
<DropZone
multiple
accept="image/*"
maxSize={2 * 1024 * 1024}
label="Drop images here"
onDrop={(files) => console.log(files)}
/>LoadingOverlay
OverlaysFull-screen loading state with spinner and message.
import { LoadingOverlay } from '@gusvega/ui';
<LoadingOverlay isVisible={true} message="Loading..." />CommandPalette
OverlaysCmd+K style command palette with grouped commands, keyboard navigation (↑↓ Enter Esc), shortcut badges, and fuzzy search.
import { CommandPalette } from '@gusvega/ui';
import { useState } from 'react';
const COMMANDS = [
{ id: 'new-file', label: 'New File', shortcut: '⌘+N', group: 'File', action: () => {} },
{ id: 'save', label: 'Save', shortcut: '⌘+S', group: 'File', action: () => {} },
{ id: 'find', label: 'Find in Files', shortcut: '⌘+⇧+F', group: 'Edit', action: () => {} },
{ id: 'dark-mode', label: 'Toggle Dark Mode', group: 'View', action: () => {} },
];
const [open, setOpen] = useState(false);
<Button onClick={() => setOpen(true)}>Open Palette</Button>
<CommandPalette
commands={COMMANDS}
open={open}
onClose={() => setOpen(false)}
/>InfoBox
Data DisplayHighlighted info/tip box with type variants.
Pro Tip
You can use keyboard shortcuts for faster workflow.
import { InfoBox } from '@gusvega/ui';
<InfoBox
title="Pro Tip"
message="You can use keyboard shortcuts for faster workflow."
type="tip"
/>IconTabs
Data DisplayTabs component with icons and text labels.
import { IconTabs } from '@gusvega/ui';
<IconTabs tabs={[
{ id: 'home', label: 'Home', icon: '🏠', content: <div>Home content</div> },
{ id: 'settings', label: 'Settings', icon: '⚙️', content: <div>Settings content</div> },
]} />CodeBlockAdvanced
Data DisplayCode display with syntax highlighting and copy button.
const hello = () => console.log('world');import { CodeBlockAdvanced } from '@gusvega/ui';
<CodeBlockAdvanced
language="javascript"
code="const hello = () => console.log('world');"
/>AdvancedBadge
Data DisplayBadge variant with removable close button.
import { AdvancedBadge } from '@gusvega/ui';
<div className="flex gap-2 flex-wrap">
<AdvancedBadge label="React" onRemove={() => {}} />
<AdvancedBadge label="TypeScript" variant="success" onRemove={() => {}} />
<AdvancedBadge label="Tailwind" variant="warning" />
</div>ColorModeToggle
ThemingToggle between light and dark mode. Requires ThemeProvider at the app root.
import { ThemeProvider, ColorModeToggle, useTheme } from '@gusvega/ui';
// 1. Wrap your app
<ThemeProvider defaultColorMode="system">
<App />
</ThemeProvider>
// 2. Place the toggle anywhere inside
<ColorModeToggle />
// 3. Or use the hook directly
const { resolvedColorMode, toggleColorMode } = useTheme();Heading
TypographySemantic heading component. Level maps to h1–h6 with sensible default sizes and weights.
Page Title (h1)
Section Heading (h2)
Subsection (h3)
Card Title (h4)
Label (h5)
Caption (h6)
import { Heading } from '@gusvega/ui';
<Heading level={1}>Page Title</Heading>
<Heading level={2}>Section Heading</Heading>
<Heading level={3}>Subsection</Heading>
<Heading level={4} weight="medium">Card Title</Heading>
// Override size independently of level
<Heading level={1} size="2xl">Smaller h1</Heading>Text
TypographyBody text with variant, size, weight, and polymorphic as prop.
Default body text
Muted helper text
Error message
Success message
Warning message
Informational note
Small medium weight
import { Text } from '@gusvega/ui';
<Text>Default body text</Text>
<Text variant="muted">Muted helper text</Text>
<Text variant="destructive">Error message</Text>
<Text variant="success">Success message</Text>
<Text variant="warning">Warning message</Text>
<Text variant="info">Info message</Text>
<Text size="sm" weight="medium">Small medium text</Text>
<Text as="span" truncate>Long text that gets truncated...</Text>Prose
TypographyStyled wrapper for markdown or MDX content. Handles all HTML elements with consistent typography.
Getting Started
Install the library with npm install @gusvega/ui and import the styles.
- Import
@gusvega/ui/style.cssin your app entry - Wrap with
ThemeProviderfor dark mode - Import and use any component
Built on Tailwind CSS with zero runtime overhead.
import { Prose } from '@gusvega/ui';
<Prose>
<h2>Getting Started</h2>
<p>Install the library with <code>npm install @gusvega/ui</code>.</p>
<ul>
<li>Import the CSS</li>
<li>Add ThemeProvider</li>
<li>Use components</li>
</ul>
<blockquote>Built on Tailwind CSS with zero runtime overhead.</blockquote>
</Prose>FadeIn
AnimationsFade + slide up when scrolled into view. FadeInStagger sequences children.
import { FadeIn, FadeInStagger } from '@gusvega/ui';
<FadeIn>
<Card>Fades in on scroll</Card>
</FadeIn>
<FadeInStagger stagger={100}>
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</FadeInStagger>SlideIn
AnimationsSlide from any direction with a fade. Uses intersection observer.
import { SlideIn } from '@gusvega/ui';
<SlideIn from="left">
<div>Slides from left</div>
</SlideIn>
<SlideIn from="right" delay={100}>
<div>Slides from right</div>
</SlideIn>
<SlideIn from="bottom" delay={200}>
<div>Slides from bottom</div>
</SlideIn>ScaleIn
AnimationsScale up from a smaller size with spring easing. Great for cards and images.
import { ScaleIn } from '@gusvega/ui';
<ScaleIn from={0.85}>
<Card>Scales in with spring</Card>
</ScaleIn>BlurIn
AnimationsBlur-to-sharp reveal with a subtle y-offset. Ideal for hero text.
Blur to sharp reveal
Animates opacity, blur, and y-offset simultaneously
import { BlurIn } from '@gusvega/ui';
<BlurIn blur={12} offset={10}>
<Heading level={1}>Hero Title</Heading>
</BlurIn>CountUp
AnimationsAnimated number counter that triggers when scrolled into view.
import { CountUp } from '@gusvega/ui';
<CountUp from={0} to={10000} suffix="+" separator="," />
<CountUp from={0} to={99.9} decimals={1} suffix="%" duration={2000} />
<CountUp from={0} to={4200} prefix="$" separator="," />TypeWriter
AnimationsCharacter-by-character text animation. Cycles through an array of words.
import { TypeWriter } from '@gusvega/ui';
// Single word
<TypeWriter words="Hello, World!" speed={60} />
// Cycling words
<TypeWriter
words={['developer', 'designer', 'builder']}
speed={80}
loop
/>LineChart
ChartsMulti-series line chart. Requires recharts peer dependency.
import { LineChart } from '@gusvega/ui';
<LineChart
data={data}
xKey="month"
series={[
{ key: 'revenue', label: 'Revenue' },
{ key: 'users', label: 'Users' },
]}
/>BarChart
ChartsGrouped or stacked bar chart with rounded corners.
import { BarChart } from '@gusvega/ui';
<BarChart
data={data}
xKey="month"
series={[{ key: 'revenue', label: 'Revenue' }]}
rounded
/>
// Stacked
<BarChart data={data} xKey="month" series={[...]} stacked />AreaChart
ChartsArea chart with gradient fill. Supports stacked mode.
import { AreaChart } from '@gusvega/ui';
<AreaChart
data={data}
xKey="month"
series={[
{ key: 'revenue', label: 'Revenue' },
{ key: 'users', label: 'Users' },
]}
fillOpacity={0.15}
/>PieChart
ChartsPie and donut charts for proportional data.
Pie
Donut
import { PieChart } from '@gusvega/ui';
<PieChart
data={[
{ name: 'Mobile', value: 55 },
{ name: 'Desktop', value: 32 },
{ name: 'Tablet', value: 13 },
]}
donut
/>SparkLine
ChartsCompact inline chart for trends in tables or stat cards.
import { SparkLine } from '@gusvega/ui';
// In a stat card
<SparkLine data={[4, 7, 2, 9, 5, 11, 8]} type="area" height={40} />
<SparkLine data={[4, 7, 2, 9, 5, 11, 8]} type="bar" height={40} />
<SparkLine data={[4, 7, 2, 9, 5, 11, 8]} type="line" height={40} />Grid
LayoutCSS grid with responsive column counts. GridItem supports colSpan and rowSpan.
import { Grid, GridItem } from '@gusvega/ui';
// Fixed columns
<Grid cols={3} gap={4}>
<div>Col 1</div>
<div>Col 2</div>
<div>Col 3</div>
</Grid>
// Responsive
<Grid cols={{ base: 1, md: 2, lg: 3 }} gap={4}>
<GridItem colSpan={2}>Wide item</GridItem>
<div>Normal</div>
</Grid>AspectRatio
LayoutPreserves aspect ratio for media containers. Supports presets and numeric ratios.
16/9
4/3
1/1
import { AspectRatio } from '@gusvega/ui';
<AspectRatio ratio="16/9">
<img src="..." className="w-full h-full object-cover rounded-lg" />
</AspectRatio>
// Custom ratio
<AspectRatio ratio={4 / 1}>
<div className="w-full h-full bg-neutral-100 rounded-lg" />
</AspectRatio>ScrollArea
LayoutOverflow container with a styled thin scrollbar using semantic tokens.
Vertical
Horizontal
import { ScrollArea } from '@gusvega/ui';
<ScrollArea height={200}>
{items.map(item => <div key={item}>{item}</div>)}
</ScrollArea>
// Horizontal scroll
<ScrollArea direction="horizontal" height={80}>
<div className="flex gap-2 w-max">...</div>
</ScrollArea>Sticky
LayoutSticky-positioned wrapper. top, bottom, and zIndex props.
import { Sticky } from '@gusvega/ui';
// Sticky sidebar header
<Sticky top={64} zIndex={10}>
<nav>Always visible while scrolling</nav>
</Sticky>NumberInput
FormNumeric input with increment and decrement buttons. Clamps to min/max.
import { NumberInput } from '@gusvega/ui';
const [val, setVal] = useState(5);
<NumberInput value={val} onChange={setVal} min={0} max={100} step={1} />
<NumberInput value={val} onChange={setVal} step={0.5} />
<NumberInput value={val} onChange={setVal} disabled />PinInput
FormIndividual character boxes for OTP and PIN codes. Supports paste, arrow keys, masking.
6-digit OTP
Value: —
import { PinInput } from '@gusvega/ui';
const [pin, setPin] = useState('');
// 6-digit OTP
<PinInput
length={6}
value={pin}
onChange={setPin}
onComplete={(v) => console.log('Done:', v)}
/>
// Masked 4-digit PIN
<PinInput length={4} value={pin} onChange={setPin} mask />TagInput
FormType to add tags. Press Enter or comma to confirm. Backspace removes last tag.
2 / 8
import { TagInput } from '@gusvega/ui';
const [tags, setTags] = useState(['react', 'typescript']);
<TagInput
value={tags}
onChange={setTags}
placeholder="Add tag..."
maxTags={8}
/>ColorPicker
FormColor swatch with hex text input. Click the swatch to open the native color picker.
import { ColorPicker } from '@gusvega/ui';
const [color, setColor] = useState('#6366f1');
<ColorPicker value={color} onChange={setColor} />
// Swatch only
<ColorPicker value={color} onChange={setColor} showInput={false} />FileInput
FormSimple styled file button that triggers a hidden file input. For full drag-and-drop see FileUpload.
import { FileInput } from '@gusvega/ui';
<FileInput
label="Choose file"
accept="image/*"
onChange={(files) => console.log(files)}
/>
<FileInput label="Upload CSV" accept=".csv" disabled />ProgressRing
FeedbackCircular progress indicator. Shows percentage completion in a ring.
import { ProgressRing } from '@gusvega/ui';
<ProgressRing value={72} />
<ProgressRing value={45} size="sm" />
<ProgressRing value={100} />