@duckviz/ui
Radix-based UI primitives and CSS theme system for DuckViz components.
@duckviz/ui provides lightweight, accessible UI components built on Radix UI primitives, plus a CSS custom property theme system. It's the shared design layer used by all other DuckViz packages.
Installation
npm install @duckviz/ui react react-domCSS auto-injection
Styles are bundled into the package and auto-injected when you import any component. No separate CSS import is needed.
Theme system
<DuckvizThemeProvider>
Wraps your app with CSS custom properties for theming. All DuckViz components read these variables.
import { DuckvizThemeProvider } from "@duckviz/ui";
<DuckvizThemeProvider preset="terracotta" mode="dark">
{children}
</DuckvizThemeProvider>Prop
Type
Built-in presets
10 presets are available out of the box:
| Preset | Primary color | Description |
|---|---|---|
terracotta | #c45a2c | Warm earth tones (default) |
ocean | Blue | Cool ocean blues |
forest | Green | Deep forest greens |
slate | Gray | Neutral slate gray |
lavender | Purple | Soft lavender |
ember | Orange-red | Warm embers |
midnight | Dark blue | Deep midnight blue |
copper | Copper | Warm copper tones |
rose | Pink | Soft rose |
sage | Olive | Earthy sage green |
import { ALL_PRESETS } from "@duckviz/ui";
// ALL_PRESETS is a readonly array of all preset objects
// Each preset has: { name, label, seeds: { primary, ... } }Custom themes with buildThemeTokens()
Create a custom theme from a set of seed colors:
import { buildThemeTokens, DuckvizThemeProvider } from "@duckviz/ui";
const customTokens = buildThemeTokens({
primary: "#2563eb", // Blue primary
surface: "#f8fafc", // Light surface
text: "#0f172a", // Dark text
border: "#e2e8f0", // Light border
});
<DuckvizThemeProvider preset={customTokens} mode="light">
{children}
</DuckvizThemeProvider>useColorScheme()
React hook that returns the current color scheme:
import { useColorScheme } from "@duckviz/ui";
function MyComponent() {
const { mode, resolvedMode } = useColorScheme();
// mode: "light" | "dark" | "auto"
// resolvedMode: "light" | "dark" (resolved from system if "auto")
}Component library
All components are built on Radix UI primitives for accessibility and keyboard navigation.
Current version: 0.6.0. Recent additions:
0.6.0—Flex.gapaccepts Mantine-style tokens ("xs" | "sm" | "md" | "lg" | "xl"→10|12|16|20|32px). Previously token strings emitted invalid CSS (gap: lg) and silently dropped to0. New exported typeFlexGapToken. Raw numbers and CSS lengths still pass through untouched.0.5.0—Modalalways satisfies Radix's a11y contract. Chromeless modals (notitle) no longer warn. NewariaDescriptionprop renders a visually-hiddenDialog.Description; the component opts out of the describedby warning when unset.0.4.0—Drawer.position="bottom"slides up from the viewport edge (height viasize).Drawer.nonBlockinglets pointer events through to content behind — useful for "tweak + regenerate" flows over a live dashboard.
Layout
| Component | Description |
|---|---|
Flex | Flexbox layout container. gap accepts number, CSS length, or Mantine token (xs/sm/md/lg/xl) |
Text | Text display with size and color props |
XDiv | Design-token-aware layout container with border, padding, width, height props |
ScrollArea | Scrollable container with custom scrollbar styling |
Form controls
| Component | Description |
|---|---|
Button | Primary action button with variants: filled, subtle, outline, default, danger |
ActionIcon | Icon-only button (square) |
TextInput | Text input field with label, error, and section slots |
Textarea | Multi-line text input |
Checkbox | Checkbox with label |
Dropzone | File drop zone for drag-and-drop uploads |
Feedback
| Component | Description |
|---|---|
Badge | Status badges and labels |
Alert | Alert banners |
Loader | Loading spinner |
Overlays
| Component | Description |
|---|---|
Modal | Dialog modal with title and close button |
Drawer | Side drawer panel |
Popover | Popover floating panel (trigger + content) |
Tooltip | Hover tooltip |
Navigation
| Component | Description |
|---|---|
Menu | Dropdown menu (trigger + content + items) |
Tabs | Tab navigation (triggers + content panels) |
NavLink | Sidebar navigation link |
Stepper | Multi-step progress indicator |
Pagination | Page navigation controls |
Accordion | Collapsible content sections |
Example: Button
import { Button } from "@duckviz/ui";
<Button variant="filled" size="md" onClick={handleClick}>
Generate Report
</Button>
<Button variant="outline" size="sm" leftSection={<IconPlus />}>
Add Widget
</Button>
<Button variant="danger" size="sm">
Delete
</Button>Example: Modal
import { Modal } from "@duckviz/ui";
<Modal
open={isOpen}
onOpenChange={setIsOpen}
title="Confirm Action"
size={480}
>
<p>Are you sure you want to proceed?</p>
<Button onClick={() => setIsOpen(false)}>Cancel</Button>
<Button variant="filled" onClick={handleConfirm}>Confirm</Button>
</Modal>Example: Tabs
import { Tabs, TabsTrigger, TabsContent } from "@duckviz/ui";
<Tabs defaultValue="overview">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="details">Details</TabsTrigger>
<TabsContent value="overview">
Overview content here
</TabsContent>
<TabsContent value="details">
Details content here
</TabsContent>
</Tabs>Icons
@duckviz/ui/icons exports inline SVG icon components (no icon font dependency):
import { IconPlus, IconTrash, IconRefresh } from "@duckviz/ui/icons";
<IconPlus size={16} />CSS custom properties
The theme system sets --app-* CSS custom properties on the root element. Key token groups:
--app-background-*— page and surface backgrounds--app-surface-*— card and panel surfaces--app-text-*— text colors (primary, secondary, subtle)--app-border-*— border colors--app-primary-*— brand primary color variants--app-chart-*— chart color palette (10 colors)