Skip to content

Repository files navigation

cable-ui

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.

License Types Dependencies

Why

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. Set data-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/.

Install

npm install cable-ui

Import 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>
  );
}

Theming

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.

Components

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

Accessibility notes

  • 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-describedby so 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-selected and aria-controls on each tab.
  • Table marks sortable headers with aria-sort and toggles through ascending, descending, and unsorted.

Local development

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.

Tech decisions

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 with divs.
  • 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 cx helper instead of clsx. One five-line function removes a dependency and does exactly what the library needs.

Author

Built by Filipe Spanghero. More work at filipe.span.dev.br.

License

MIT

About

Minimal, accessible React + TypeScript component library. 20 components, zero runtime deps, light/dark tokens, live showcase.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages