@dynamic-labs-sdk/droplet
v1.17.0
Published
Dynamic Labs' React component library. Built on Radix UI primitives, Tailwind CSS v4, and an OKLCH-based design token system. Ships with 35+ components, animated icons, dark mode, and a live Storybook.
Readme
@dynamic-labs-sdk/droplet
Dynamic Labs' React component library. Built on Radix UI primitives, Tailwind CSS v4, and an OKLCH-based design token system. Ships with 35+ components, animated icons, dark mode, and a live Storybook.
Installation
npm install @dynamic-labs-sdk/droplet
# or
pnpm add @dynamic-labs-sdk/droplet
# or
yarn add @dynamic-labs-sdk/dropletPeer dependencies: react >= 18, react-dom >= 18, tailwindcss >= 4. Next.js (>= 14) is optional.
Setup
Droplet ships Tailwind CSS v4 utility classes baked into its compiled output. You must (1) have Tailwind v4 configured in your app, (2) import the design tokens stylesheet, and (3) tell Tailwind to scan droplet's dist/ so its utility classes are emitted into your stylesheet.
1. Install and configure Tailwind v4
If you don't already have Tailwind v4, follow the Tailwind v4 install guide. The @tailwindcss/vite plugin is the simplest path for Vite/Next:
// vite.config.ts
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [tailwindcss()],
});2. Import the design tokens stylesheet
Import once at your app root. This provides the full color, shadow, radius, and typography token system and is required for all components to render correctly.
import '@dynamic-labs-sdk/droplet/styles/globals.css';3. Add droplet to Tailwind's @source paths
Tailwind v4 only scans the consuming project by default, so it will miss the utility classes inside droplet's compiled JS. Add an @source directive to your app's main stylesheet:
/* app.css */
@import 'tailwindcss';
@source "../node_modules/@dynamic-labs-sdk/droplet/dist";Adjust the relative path to match where your stylesheet lives. Without this step, components will render unstyled.
Usage
import {
Button,
Card,
CardContent,
CardHeader,
CardTitle,
Badge,
} from '@dynamic-labs-sdk/droplet';
export const Example = () => (
<Card>
<CardHeader>
<CardTitle>
My Feature
<Badge variant="brand">New</Badge>
</CardTitle>
</CardHeader>
<CardContent>
<Button>Get started</Button>
</CardContent>
</Card>
);Components
UI Primitives
Thin wrappers over Radix UI primitives with full token-based styling.
| Export | Description |
| ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| Accordion, AccordionItem, AccordionTrigger, AccordionContent | Collapsible content sections |
| AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogAction, AlertDialogCancel, ... | Destructive action confirmation modal |
| Avatar, AvatarImage, AvatarFallback | User avatar with initials fallback |
| Badge | Semantic status/label chip. Variants: default, secondary, outline, destructive, brand, success, warning, process |
| Button | Primary interactive element. Variants: default, secondary, outline, ghost, destructive, link |
| Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter | Composable card layout |
| Checkbox | Accessible checkbox with draw-on animation |
| Collapsible, CollapsibleTrigger, CollapsibleContent | Togglable content region |
| Command, CommandInput, CommandList, CommandItem, CommandGroup, ... | Headless command palette (cmdk) |
| Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogBody, DialogFooter, ... | Accessible modal dialog |
| DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, ... | Keyboard-navigable dropdown |
| Icon | Wrapper for HugeIcons with consistent stroke width |
| Input | Text input with field shadow system |
| Kbd | Keyboard shortcut display (⌘K) |
| Label | Form label linked to control |
| Popover, PopoverTrigger, PopoverContent, PopoverAnchor | Floating content panel |
| Progress | Progress bar, determinate and indeterminate |
| RadioGroup, RadioGroupItem | Radio button group with hover dot |
| ScrollArea, ScrollBar | Custom-styled scrollable container |
| SearchInput | Input with leading search icon |
| Select, SelectTrigger, SelectContent, SelectItem, SelectValue, ... | Radix-backed select |
| SelectableCard | Card with embedded checkbox/radio state |
| Separator | Horizontal or vertical divider |
| Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle, SheetFooter, ... | Side drawer (top / right / bottom / left) |
| Skeleton | Shimmer loading placeholder |
| Toaster | Toast notifications (Sonner) |
| Spinner | Animated loading indicator |
| Switch | Toggle switch with data-slot label integration |
| Table, TableHeader, TableBody, TableRow, TableHead, TableCell, TableFooter, TableCaption | Data table |
| Tabs, TabsList, TabsTrigger, TabsContent | Tabbed navigation, underline and pill variants |
| Textarea | Multiline text input |
| Tooltip, TooltipTrigger, TooltipContent, TooltipProvider | Hover tooltip |
| Well, WellReveal | Inset container for grouped settings |
Project Patterns
Higher-level components composed from primitives, designed for dashboard and settings UIs.
| Export | Description |
| --------------------------------------------------- | ------------------------------------------------------------------- |
| AskAiSheet | Side panel for AI assistant integration |
| AssetIcon | Chain/token icon with fallback initial |
| CodeBlock | Syntax-highlighted code block with copy button |
| CopyButton | Clipboard copy trigger with animated check |
| DangerZone | Destructive settings section gated by confirmation |
| DomainList | Editable list of allowed domains with add/remove |
| EmptyState | Zero-data placeholder with icon, title, description, CTA |
| InfoBanner | Contextual banner. Variants: info, warning, success, danger |
| MetricCard | KPI stat card with value, label, and trend |
| PageActions | Sticky bottom action bar with primary/secondary slots |
| PageSkeleton, StatCardSkeleton, TableSkeleton | Page-level loading skeletons |
| ProductPageLayout | Full-page scaffold: header, tabs, content, sidebar |
| SettingsSection | Labeled section wrapper for settings pages |
| StatusBadge | Live / inactive / pending status indicator |
| ToggleRow | Label + description + switch in a single accessible row |
| UnsavedChangesBar | Sticky bottom bar for save/discard with change tracking |
Animated Icons
Custom SVG icons with spring physics via Motion. All icons accept standard SVG props plus a className.
import {
ChevronDownIcon,
CopyCheckIcon,
SettingsIcon,
} from '@dynamic-labs-sdk/droplet';
<SettingsIcon className="size-4 text-muted-foreground" />;| Export | Description |
| ---------------------------------------------------------------------- | ---------------------------------------- |
| AnimatedIcon | Base wrapper for building animated icons |
| ArrowUpRightIcon | External link arrow |
| ChartBarIncreasingIcon | Animated bar chart |
| ChevronDownIcon | Chevron, rotates on toggle |
| ChevronRightIcon | Chevron pointing right |
| CopyIcon | Copy to clipboard |
| CopyCheckIcon | Copy → check transition |
| DollarSignIcon | Currency symbol |
| DownloadIcon | Download arrow |
| FingerprintIcon | Biometric fingerprint |
| HomeIcon | Home/dashboard |
| KeyIcon | API key / auth |
| LayoutPanelTopIcon | Layout panel |
| LinkIcon | Hyperlink |
| MoonIcon | Dark mode |
| PanelLeftCloseIcon | Collapse sidebar |
| PanelLeftOpenIcon | Expand sidebar |
| PlusIcon | Add / create |
| RefreshCWIcon | Refresh / reload |
| SearchIcon | Search magnifier |
| SettingsIcon | Settings gear |
| ShieldCheckIcon | Security / verified |
| SunIcon | Light mode |
| TerminalIcon | Code / CLI |
| UsersIcon | Users / team |
| ClaudeLogo, CursorLogo, OpenAILogo, VSCodeLogo, WindsurfLogo | MCP tool logos |
Utilities & Hooks
| Export | Description |
| ------------------------------- | ---------------------------------------------------------------------------------- |
| cn(...classes) | clsx + tailwind-merge class utility |
| useUnsavedChanges(hasChanges) | Hook for the UnsavedChangesBar — tracks dirty state and wires keyboard shortcuts |
| IconConfigProvider | Context provider for global icon configuration |
| useIconConfig() | Access icon config context |
Theming
Droplet's theme is a layer of CSS custom properties. Tailwind utility classes resolve through them (bg-brand-default → var(--brand-default)), and every component reads from the same vars — so you can fully rebrand by redefining the vars in your own stylesheet. No Tailwind config changes, no rebuild, no fork.
Quick start: change the brand color
/* your-theme.css — imported AFTER @dynamic-labs-sdk/droplet/styles/globals.css */
:root {
--brand-default: oklch(0.67 0.21 30); /* your light-mode brand */
--brand-hover: oklch(0.72 0.19 30);
--brand-accented: oklch(0.58 0.22 30);
--brand-light: oklch(0.97 0.03 30);
--ring: oklch(0.67 0.21 30);
--action: oklch(0.67 0.21 30);
}
.dark {
--brand-default: oklch(0.6 0.22 30); /* your dark-mode brand */
--brand-hover: oklch(0.68 0.19 30);
--brand-accented: oklch(0.52 0.23 30);
--brand-light: oklch(0.27 0.07 30);
--ring: oklch(0.6 0.22 30);
--action: oklch(0.6 0.22 30);
}// in your app entry, AFTER the droplet import
import '@dynamic-labs-sdk/droplet/styles/globals.css';
import './your-theme.css';That's it. Buttons, links, focus rings, text-selection highlights, sidebars — all switch to the new brand.
Cascade order matters
Your theme stylesheet must load after @dynamic-labs-sdk/droplet/styles/globals.css, or the same selectors (:root, .dark) in droplet's stylesheet will win and your overrides will silently do nothing. If you @import in a single CSS file, put droplet's import first:
@import '@dynamic-labs-sdk/droplet/styles/globals.css';
@import './your-theme.css';What's themeable
Anything in the Design tokens table below — colors, shadows, radii. The most common things to override:
| What you want to change | Tokens |
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| Brand color (buttons, focus rings, links, selection) | --brand-default, --brand-hover, --brand-accented, --brand-light, --ring, --action |
| Surfaces (page bg, cards, sidebar) | --background, --card, --bg-bottom, --bg-default, --sidebar |
| Text colors | --foreground, --muted-foreground, --text-link |
| Radius scale | --radius-sm through --radius-4xl |
| Selection highlight | --selection-bg |
| Solid button top-highlight | --button-highlight |
| Checkbox / radio hover fills | --checkbox-hover-fill, --radio-hover-fill |
For semantic colors (success/danger/warning/process), override the matching --<state>-default, --<state>-light, --text-<state> triplet.
Scoping a theme to part of your app
If you only want droplet themed inside a subtree (e.g., a marketing site reusing one component), put the overrides on a wrapper class instead of :root:
.my-theme {
--brand-default: oklch(0.67 0.21 30);
/* ... */
}<div className="my-theme">
<Button>Themed</Button>
</div>Color space
Droplet uses OKLCH throughout. If your design system is in hex/rgb, tools like oklch.com or oklch.fyi will convert. You don't have to use OKLCH in your overrides — oklch(), hex, rgb(), and hsl() all interoperate fine — but staying in OKLCH gives you perceptually uniform lightness ramps when you tune hover/accented variants.
Design tokens
All tokens are CSS custom properties defined in globals.css. Tailwind utility classes map directly to them — bg-brand-default resolves to var(--brand-default).
Color groups
| Group | Tokens |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Surfaces | --background, --card, --popover, --muted, --accent, --bg-bottom, --bg-default, --bg-accented, --bg-strong, --bg-absolute |
| Text | --foreground, --muted-foreground, --text-disabled, --text-placeholder, --text-link, --text-invert |
| Brand | --brand-default, --brand-hover, --brand-accented, --brand-light, --action |
| Semantic | --success-default/light/text, --danger-default/light/text, --warning-default/light/text, --process-default/light/text |
| Borders | --border, --border-divider, --border-default, --border-hover, --border-strong, --border-focused, --border-selected, --border-danger, --border-success, --border-process, --border-warning |
| Overlays | --overlay-hover, --overlay-pressed, --overlay-selected, --overlay-selected-hover, --overlay-md, --overlay-strong |
Shadow system
--shadow-border /* 1px outline */
--shadow-card /* card elevation */
--shadow-button /* button depth */
--shadow-input /* form field */
--shadow-input-hover /* field on hover */
--shadow-elevated /* popovers / dropdowns */
--shadow-card-content /* content area separator */Radius scale
| Token | Value |
| -------------- | ----- |
| --radius-sm | 6px |
| --radius-md | 8px |
| --radius-lg | 10px |
| --radius-xl | 12px |
| --radius-2xl | 14px |
| --radius-3xl | 16px |
| --radius-4xl | 20px |
Dark mode
Apply the .dark class to <html> to activate the dark palette. All tokens are redefined under .dark with OKLCH-matched values — brand hue is preserved, only lightness shifts.
document.documentElement.classList.toggle('dark', isDark);Storybook
Interactive component documentation with live token reference, dark mode toggle, and a11y audit on every story.
# From repo root
pnpm --filter @dynamic-labs-sdk/droplet storybook
# From packages/droplet
pnpm storybookRuns on http://localhost:4402.
Development
# Run tests
pnpm --filter @dynamic-labs-sdk/droplet test
# Watch mode
pnpm --filter @dynamic-labs-sdk/droplet test:watch
# Build (ESM + CJS + .d.ts)
pnpm --filter @dynamic-labs-sdk/droplet build
# Lint
pnpm --filter @dynamic-labs-sdk/droplet lintTech stack
| | |
| -------------- | ------------------------------------------------------------------------------- |
| Primitives | Radix UI |
| Styling | Tailwind CSS v4 via @tailwindcss/vite |
| Animations | Motion (spring physics) |
| Icons | HugeIcons |
| Variants | class-variance-authority |
| Toasts | Sonner |
| Build | tsdown (dual ESM/CJS) |
| Tests | Vitest + Testing Library |
| Docs | Storybook 8 |
