A neobrutalist React component library with hard shadows, bold colors, and accessible primitives.
npm install ice-ds
# or
yarn add ice-ds
# or
pnpm add ice-dsPeer dependencies (install separately if not already in your project):
npm install react react-domUse ice-ds directly in any HTML page — no bundler required. React and ReactDOM must be loaded first.
<!-- 1. React -->
<script crossorigin src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"></script>
<!-- 2. ice-ds styles -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/style.css" />
<!-- 3. ice-ds UMD bundle -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ice-ds.umd.js"></script>Components are then available on the IceDS global:
<script>
const { Button, Badge } = IceDS;
const e = React.createElement;
ReactDOM.createRoot(document.getElementById('root')).render(
e(Button, { variant: 'primary' }, 'Hello from ice-ds')
);
</script>Pin to a version — replace
@1.0.3with the exact version you tested against so future releases don't break your pages.
Add this once at your app root (e.g., main.tsx or _app.tsx):
import 'ice-ds/styles';If your app uses Tailwind CSS, extend your config with the ice-ds tokens so you can use the brand colors, shadows, and typography scale in your own code:
// tailwind.config.js
import { iceTailwindTokens } from 'ice-ds';
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
...iceTailwindTokens,
},
},
};import { Button } from 'ice-ds';
<Button>Click me</Button>
<Button variant="secondary">Cancel</Button>
<Button variant="destructive">Delete</Button>
<Button loading>Saving…</Button>
<Button disabled>Unavailable</Button>| Prop | Type | Default | Description |
|---|---|---|---|
variant |
primary secondary ghost destructive success accent inverse |
primary |
Visual style |
size |
sm md lg xl |
md |
Button size |
loading |
boolean |
false |
Shows spinner, disables interaction |
fullWidth |
boolean |
false |
Stretches to container width |
import { Badge } from 'ice-ds';
<Badge>Default</Badge>
<Badge variant="new">New</Badge>
<Badge variant="success">Shipped</Badge>
<Badge variant="error">Failed</Badge>| variant | default new primary success info premium warning error |
import { Card, CardEyebrow, CardTitle, CardDescription } from 'ice-ds';
<Card variant="featured" elevation="lg" interactive>
<CardEyebrow>Case study</CardEyebrow>
<CardTitle>2.1x conversion lift</CardTitle>
<CardDescription>Redesigned onboarding flow reduced drop-off by 40%.</CardDescription>
</Card>| Prop | Type | Default |
|---|---|---|
variant |
default featured success info premium primary inverse |
default |
elevation |
none sm md lg xl |
md |
interactive |
boolean |
false |
import { Alert } from 'ice-ds';
<Alert intent="success" title="Deployed!" onDismiss={() => {}}>
Your changes are live on production.
</Alert>| intent | info success warning error announce |
import { Input, Textarea, Select, Label, Field } from 'ice-ds';
// Standalone
<Input placeholder="Email address" size="md" />
<Textarea placeholder="Your message…" />
<Select>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
</Select>
// Composed field with label + validation
<Field
label="Email"
htmlFor="email"
required
error="Please enter a valid email"
hint="We'll never share your email"
>
<Input id="email" type="email" />
</Field>import { Checkbox } from 'ice-ds';
<Checkbox label="Accept terms" description="You agree to our ToS and Privacy Policy" />import { RadioGroup, RadioItem } from 'ice-ds';
<RadioGroup defaultValue="monthly">
<RadioItem value="monthly" label="Monthly" description="Billed every month" />
<RadioItem value="annual" label="Annual" description="Save 20% — billed yearly" />
</RadioGroup>import { Switch } from 'ice-ds';
<Switch label="Dark mode" size="md" intent="default" />| size | sm md lg |
| intent | default success info accent |
import {
Modal, ModalTrigger, ModalContent,
ModalHeader, ModalTitle, ModalDescription, ModalFooter,
ModalClose
} from 'ice-ds';
import { Button } from 'ice-ds';
<Modal>
<ModalTrigger asChild>
<Button>Open modal</Button>
</ModalTrigger>
<ModalContent>
<ModalHeader>
<ModalTitle>Confirm action</ModalTitle>
<ModalDescription>This cannot be undone.</ModalDescription>
</ModalHeader>
<ModalFooter>
<ModalClose asChild>
<Button variant="secondary">Cancel</Button>
</ModalClose>
<Button variant="destructive">Delete</Button>
</ModalFooter>
</ModalContent>
</Modal>import { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerTitle, DrawerBody } from 'ice-ds';
<Drawer>
<DrawerTrigger asChild>
<Button>Open drawer</Button>
</DrawerTrigger>
<DrawerContent side="right">
<DrawerHeader>
<DrawerTitle>Settings</DrawerTitle>
</DrawerHeader>
<DrawerBody>Your content here</DrawerBody>
</DrawerContent>
</Drawer>| side | left right top bottom | Default: right |
import { Dropdown, DropdownTrigger, DropdownContent, DropdownItem, DropdownSeparator } from 'ice-ds';
<Dropdown>
<DropdownTrigger asChild>
<Button variant="secondary">Options</Button>
</DropdownTrigger>
<DropdownContent>
<DropdownItem>Edit</DropdownItem>
<DropdownItem>Duplicate</DropdownItem>
<DropdownSeparator />
<DropdownItem destructive>Delete</DropdownItem>
</DropdownContent>
</Dropdown>import { Popover, PopoverTrigger, PopoverContent } from 'ice-ds';
<Popover>
<PopoverTrigger asChild>
<Button variant="ghost">More info</Button>
</PopoverTrigger>
<PopoverContent>Helpful tooltip content goes here.</PopoverContent>
</Popover>import { Navbar, NavbarContainer, NavbarBrand, NavbarNav, NavbarLink, NavbarActions } from 'ice-ds';
<Navbar variant="default" sticky>
<NavbarContainer>
<NavbarBrand>Ice</NavbarBrand>
<NavbarNav>
<NavbarLink href="/" active>Home</NavbarLink>
<NavbarLink href="/work">Work</NavbarLink>
</NavbarNav>
<NavbarActions>
<Button size="sm">Hire me</Button>
</NavbarActions>
</NavbarContainer>
</Navbar>| variant | default dark yellow pink blue |
import { Breadcrumb } from 'ice-ds';
<Breadcrumb
items={[
{ label: 'Work', href: '/work' },
{ label: 'Case Study', href: '/work/case-study' },
{ label: 'Metrics' },
]}
showHome
/>import { Tabs, TabsList, TabsTrigger, TabsContent } from 'ice-ds';
<Tabs defaultValue="overview">
<TabsList variant="underline">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="details">Details</TabsTrigger>
</TabsList>
<TabsContent value="overview">Overview content</TabsContent>
<TabsContent value="details">Details content</TabsContent>
</Tabs>| variant (TabsList) | underline pill boxed |
import { PaginationRoot } from 'ice-ds';
<PaginationRoot
page={3}
totalPages={10}
onPageChange={(p) => console.log(p)}
siblings={1}
/>import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from 'ice-ds';
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Project Alpha</TableCell>
<TableCell><Badge variant="success">Shipped</Badge></TableCell>
</TableRow>
</TableBody>
</Table>import { Stat, StatGrid } from 'ice-ds';
<StatGrid>
<Stat label="Revenue" value="$48,200" trend="up" trendValue="+12%" accent="yellow" />
<Stat label="Churn" value="2.4%" trend="down" trendValue="-0.3%" accent="green" />
</StatGrid>import { Progress } from 'ice-ds';
<Progress value={72} intent="success" size="md" showValue label="Upload" />import { Slider } from 'ice-ds';
<Slider defaultValue={[40]} intent="default" label="Volume" showValue />import { Avatar, AvatarImage, AvatarFallback, AvatarGroup } from 'ice-ds';
<Avatar size="md" shape="circle">
<AvatarImage src="/avatar.jpg" alt="Vicente" />
<AvatarFallback colorIndex={0}>VR</AvatarFallback>
</Avatar>
// Stacked group
<AvatarGroup max={4}>
{users.map((u) => (
<Avatar key={u.id}>
<AvatarImage src={u.avatar} alt={u.name} />
<AvatarFallback>{u.initials}</AvatarFallback>
</Avatar>
))}
</AvatarGroup>import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from 'ice-ds';
<Accordion type="single" collapsible>
<AccordionItem value="q1">
<AccordionTrigger>What is ice-ds?</AccordionTrigger>
<AccordionContent>A neobrutalist React component library.</AccordionContent>
</AccordionItem>
</Accordion>import { ToastProvider, ToastViewport, Toast, ToastWithIcon, ToastTitle, ToastDescription } from 'ice-ds';
// Wrap your app
<ToastProvider>
<App />
<ToastViewport />
</ToastProvider>
// Trigger a toast
<ToastWithIcon intent="success">
<ToastTitle>Saved!</ToastTitle>
<ToastDescription>Your changes have been saved.</ToastDescription>
</ToastWithIcon>import { Tooltip, TooltipProvider } from 'ice-ds';
<TooltipProvider>
<Tooltip content="This is a tooltip" variant="default" side="top">
<Button variant="ghost">Hover me</Button>
</Tooltip>
</TooltipProvider>| variant | default light yellow |
import { Spinner } from 'ice-ds';
<Spinner size="md" intent="primary" label="Loading…" />import { EmptyState } from 'ice-ds';
import { FolderOpen } from 'lucide-react';
<EmptyState
icon={FolderOpen}
title="No projects yet"
description="Create your first project to get started."
action={<Button>New project</Button>}
size="md"
/>import { Skeleton, SkeletonCard, SkeletonAvatar, SkeletonText } from 'ice-ds';
<SkeletonCard />
<SkeletonAvatar />
<SkeletonText lines={3} />
<Skeleton variant="default" className="h-10 w-40" />import { Separator } from 'ice-ds';
<Separator />
<Separator dashed />
<Separator label="OR" />
<Separator orientation="vertical" />import { Kbd, Shortcut } from 'ice-ds';
<Kbd size="md">⌘</Kbd>
<Shortcut keys={['⌘', 'K']} />Tokens are exported as TypeScript constants and also wired into Tailwind via the config extension above.
import { colors, shadows, typography } from 'ice-ds';
// Brand colors
colors['neo-yellow'] // #FFDE59 — primary CTA
colors['neo-green'] // #7ED957 — success
colors['neo-pink'] // #FF66C4 — accent / featured
colors['neo-blue'] // #5CE1E6 — info
colors['neo-purple'] // #C678DD — premium
colors['neo-black'] // #0F0F0F — all text, borders
colors['neo-offwhite'] // #FFFDF5 — default background
colors['neo-red'] // #EF4444 — error
colors['neo-amber'] // #F59E0B — warningTailwind shadow tokens (hard offset, no blur — the neobrutalist signature):
shadow-brutal-sm → 3px 3px 0 0 #0F0F0F
shadow-brutal → 4px 4px 0 0 #0F0F0F
shadow-brutal-md → 6px 6px 0 0 #0F0F0F
shadow-brutal-lg → 10px 10px 0 0 #0F0F0F
shadow-brutal-xl → 14px 14px 0 0 #0F0F0F
| Category | Components |
|---|---|
| Basic | Button, Badge, Card, Alert |
| Form | Input, Textarea, Select, Label, Field, Checkbox, Radio, Switch |
| Dialog | Modal, Drawer, Dropdown, Popover |
| Navigation | Navbar, Breadcrumb, Tabs, Pagination |
| Data Display | Table, Stat, Progress, Slider, Avatar, Accordion |
| Feedback | Toast, Tooltip, Spinner, LoadingOverlay, EmptyState |
| Misc | Skeleton, Separator, Kbd |
neo-blacktext on every colored surface — no exceptions.neo-yellowis the only primary CTA color.neo-pinkandneo-purpleare decorative — never semantic.- Hard offset shadows only (
shadow-brutal-*tokens). - Hover = translate -2/-2 + shadow grows. Active = both collapse to 0.
- No italics. Bold for emphasis.
git clone https://github.com/highcenburg/ice-ds.git
cd ice-ds
npm install
npm run storybook # http://localhost:6006
npm run build # outputs to dist/
npm run type-check # TypeScript validationLive docs + component playground run in Storybook. Every component has per-variant stories and an AllVariants overview. Run the a11y panel and verify AA compliance before shipping a new component.
MIT