wipsa
v0.1.2
Published
A precision design system for serious data work — warm, quiet, intentional.
Maintainers
Readme
Wipsa
A precision design system for serious data work — warm, quiet, intentional.
Wipsa is built for interfaces where the data is the point: research tools, clinical dashboards, signal monitors, scientific instruments. It uses OKLCH colour, spring physics, and Radix UI primitives to produce interfaces that feel like professional instruments rather than consumer apps.
Install
npm install wipsa framer-motionWipsa requires framer-motion as a peer dependency. React 18 or 19.
Setup
Wrap your app in WipsaProvider and import the stylesheet:
// main.tsx
import 'wipsa/dist/wipsa.css';
import { WipsaProvider } from 'wipsa';
import { App } from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<WipsaProvider defaultTheme="dark">
<App />
</WipsaProvider>
);The provider sets data-theme on <html> and exposes theme/density controls via context.
Quick start
import { Button, Card, Stack, Badge, useTheme } from 'wipsa';
function Example() {
const { theme, setTheme } = useTheme();
return (
<Card>
<Stack gap="md">
<Badge variant="success">Signal nominal</Badge>
<Button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Toggle theme
</Button>
</Stack>
</Card>
);
}Components
UI Components
| Component | Description | Radix primitive |
|-----------|-------------|-----------------|
| Button | Primary, ghost, destructive variants; loading state | — |
| Card | Gradient / glow-border surface treatments | — |
| Input | With label, hint, error; full ARIA support | — |
| Badge | Semantic variants: default, success, warning, error | — |
| Toggle | Accessible on/off switch | @radix-ui/react-switch |
| Select | Dropdown select with search | @radix-ui/react-select |
| Tabs | Horizontal tab bar | @radix-ui/react-tabs |
| Dialog | Modal with overlay | @radix-ui/react-dialog |
| Dropdown | Context menu / action menu | @radix-ui/react-dropdown-menu |
| Tooltip | Hover/focus tooltip | @radix-ui/react-tooltip |
| Popover | Click-triggered overlay | @radix-ui/react-popover |
| Slider | Range input | — |
| Avatar | Image or initials fallback | — |
| Spinner | Loading indicator | — |
| DataTable | Sortable, paginated table | @tanstack/react-table |
Edge States
| Component | Description |
|-----------|-------------|
| Skeleton | Shimmer placeholder matching loaded content dimensions |
| EmptyState | Structured empty state: icon + heading + description + action |
| ErrorBoundary | React error boundary with default or custom fallback |
Toasts
import { ToastProvider, useToast } from 'wipsa';
// Wrap your app:
<ToastProvider>
<App />
</ToastProvider>
// Use in any component:
const { toast } = useToast();
toast({ title: 'Session saved.', variant: 'success' });Charts
All charts use D3 for scales and SVG rendering, Framer Motion for animation. Data is passed as plain arrays.
| Chart | Data shape |
|-------|-----------|
| LineChart | { data: { x: number, y: number }[][], series: SeriesConfig[] } |
| BarChart | { data: { label: string, values: number[] }[], series: BarSeriesConfig[] } |
| AreaChart | { data: { x: number, y: number }[][], series: AreaSeriesConfig[] } |
| DonutChart | { data: DonutDatum[] } — { label, value, color? } |
| ScatterChart | { data: ScatterDatum[][], series: ScatterSeriesConfig[] } |
| GaugeChart | { value, min, max, label } |
| MetricCard | { value, label, unit?, trend?, sparkData? } |
| SparkLine | { data: number[], color? } |
| WaveformChart | { data: number[], sampleRate: number } |
import { LineChart } from 'wipsa';
<LineChart
data={[points]}
series={[{ label: 'HbO₂', color: 'var(--quint-signal-a)' }]}
xLabel="Time (s)"
yLabel="Concentration (μM)"
/>Layout
Layout primitives handle composition — use them instead of raw div + flex.
| Primitive | Purpose |
|-----------|---------|
| Stack | Vertical or horizontal stack with gap |
| Split | Two-pane layout with ratio control (golden, 1/3, 1/2…) |
| Bento | Grid layout with featured-item spanning support |
| Cluster | Wrapping flex row — tags, badges, inline elements |
| Container | Max-width content container with responsive padding |
| Sidebar | Fixed-width sidebar + fluid main content |
Theming
Switching themes
const { theme, setTheme } = useTheme(); // 'light' | 'dark'
const { density, setDensity } = useTheme(); // 'default' | 'compact'Overriding tokens
All design decisions are CSS custom properties on :root. Override them anywhere:
:root {
--accent-6: oklch(0.62 0.11 220); /* swap teal accent for blue */
}See guidance.md in the repository for the full token reference and design philosophy.
Accessibility
- All interactive components use Radix UI primitives (keyboard navigation, ARIA, focus management)
prefers-reduced-motionis detected inWipsaProviderand disables spring animations- Colour is never the sole information carrier — status uses icon + colour
- WCAG 2.2 AA contrast targets throughout
Browser support
Modern browsers with OKLCH colour support: Chrome 111+, Safari 15.4+, Firefox 113+.
Design philosophy
Wipsa has a precise point of view. The full rationale — colour system, typography, motion, perception principles — is documented in guidance.md.
The short version: warm neutrals, restrained chroma, spring physics, Radix primitives. No Tailwind. No shadcn. Every decision is traceable to a reason.
License
MIT
