A minimal, accessible React component library built with TypeScript. Twenty components with zero runtime dependencies, driven entirely by CSS custom properties so light and dark themes are a single attribute swap.
Live demo · Versão em português
The showcase reads in English at / and Portuguese at /pt. The language comes
from the URL, so a shared link opens in the language it was read in. Component
names, props and the code in every example stay in English: each specimen shows
its source next to the rendered component, and translating one without the other
would make them disagree.
Most component libraries pull in a styling framework, an icon set, and a handful of utility packages before you render a single button. cable-ui takes the opposite bet: plain React, plain CSS, and a small token file that every component reads from. The result is easy to theme, easy to audit, and small to ship.
- Accessible by default. Focus management, ARIA wiring, and keyboard
navigation are built into each component rather than bolted on. The Modal
traps focus and restores it on close, Tabs move with the arrow keys, and the
Table exposes
aria-sort. - Themeable. One file,
tokens.css, drives color, spacing, radius, and motion. Setdata-theme="dark"on any ancestor and the whole tree follows. - Typed. Every prop is typed, and the build emits declaration files.
- Tested. 59 tests across 20 components with Vitest and Testing Library, focused on behavior and accessibility rather than snapshots.
The showcase site is a hand-built React page that renders every component live,
with a code toggle and a light/dark switch. Its source lives in demo/.
npm install cable-uiImport the component you need and the stylesheet once, at the root of your app:
import { Button, ToastProvider, useToast } from 'cable-ui';
import 'cable-ui/styles.css';
function SaveButton() {
const { toast } = useToast();
return (
<Button onClick={() => toast({ title: 'Saved', variant: 'success' })}>
Save changes
</Button>
);
}
export function App() {
return (
<ToastProvider>
<SaveButton />
</ToastProvider>
);
}All visual decisions live in CSS custom properties. Override any of them in your own stylesheet to rebrand the whole library, or flip a single attribute to switch themes:
<div data-theme="dark">
<!-- every cable-ui component here renders in dark mode -->
</div>/* Rebrand the primary color everywhere */
:root {
--cable-primary: #4f46e5;
--cable-primary-hover: #4338ca;
}The library respects prefers-reduced-motion: animations shorten or stop when
the user asks their system to reduce motion.
| Component | Notes |
|---|---|
Button |
Five variants, three sizes, loading and icon slots |
Badge |
Semantic colors with an optional status dot |
Spinner |
Accessible loading indicator |
Avatar |
Image with automatic initials fallback |
Skeleton |
Text, rectangle, and circle placeholders |
Field |
Shared label, hint, and error shell for custom controls |
Input |
Text input with hint and error wiring |
Textarea |
Multi-line input |
Select |
Styled native select, keyboard and screen-reader friendly |
Checkbox |
With label and hint |
RadioGroup / Radio |
Grouped choice with a shared name |
Switch |
Toggle with a switch role |
Card |
Composable header, body, and footer slots |
Alert |
Four tones, optional dismiss |
Progress |
Determinate and indeterminate |
Tabs |
Roving tabindex, arrow-key navigation |
Accordion |
Single or multiple open, region panels |
Modal |
Portal, focus trap, Esc to close, scroll lock |
Tooltip |
Shows on hover and keyboard focus |
Toast |
Provider plus useToast hook, four tones, auto-dismiss |
Table |
Generic and typed, click-to-sort with aria-sort |
- Modal renders through a portal, moves focus inside on open, traps Tab and Shift+Tab, restores focus to the trigger on close, and locks body scroll.
- Tooltip links the trigger and bubble with
aria-describedbyso the text is announced, and it appears on focus, not just hover. - Tabs follow the WAI-ARIA tabs pattern: one tab stop, arrow keys to move,
and
aria-selectedandaria-controlson each tab. - Table marks sortable headers with
aria-sortand toggles through ascending, descending, and unsorted.
npm install
npm run dev # showcase site at http://localhost:5173
npm test # run the test suite
npm run typecheck # type-check without emitting
npm run build # build the library to dist/
npm run build:site # build the showcase to site-dist/The showcase is deployed to Cloudflare Workers static assets with
npx wrangler deploy after npm run build:site.
A few choices worth explaining, since they shaped the whole library:
- CSS variables over a styling framework. Tokens as custom properties keep runtime cost at zero and make theming a matter of overriding a value. There is no build step tied to class generation and no framework version to track.
- Native elements first. Select wraps a real
<select>, Checkbox and Radio wrap real inputs. That inherits form behavior, validation, and platform accessibility for free instead of rebuilding it withdivs. - Compound components for structure. Card, Tabs, and Accordion expose
sub-components (
Card.Header,Tabs.Panel) so layout stays declarative and the parent coordinates state through context. - A tiny
cxhelper instead ofclsx. One five-line function removes a dependency and does exactly what the library needs.
Built by Filipe Spanghero. More work at filipe.span.dev.br.
MIT