Skip to content
cennge-ui
GitHubv0.3.1, changelog

Introduction

Tactile, dark-first React components on Radix UI and Tailwind CSS v4, installed from npm and updated like any other package.

A package you update#

Copy-paste kits such as shadcn/ui put each component's source in your repository. You own every line, and a fix made upstream reaches an app only when someone copies it in again.

cennge-ui aims for that kind of finish, but ships the way Mantine does: as a regular npm package. The components live in node_modules, a fix is made once in the library, and npm update brings it to every app that uses it. You still restyle them as much as you like, without forking: every colour, surface and radius is a --cennge-* CSS variable, className is merged with tailwind-merge so your classes win, and components with inner parts, such as Input and Dialog, take classes for them through classNames.

What it looks like#

A Card, an Input, a Select, a Switch, a Badge and a Button, as they come. Change the version and publish: the button waits, then a toast confirms. Switch the site's theme and accent and the card follows.

payload-cache

New release

CI passing
release-card.tsx
import { useState } from "react";
import { Badge, Button, Card, CardBody, CardFooter, CardHeader, Input, Select, SelectItem, Switch, toast } from "cennge-ui";

export default function ReleaseCard() {
  const [version, setVersion] = useState("1.4.0");
  const [tag, setTag] = useState<string | null>("latest");
  const [isPublishing, setPublishing] = useState(false);

  async function publish() {
    setPublishing(true);
    // Stands in for the real request.
    await new Promise((resolve) => setTimeout(resolve, 1200));
    setPublishing(false);
    toast.success("Published", { description: `payload-cache@${version} is on npm as ${tag}` });
  }

  return (
    <Card className="w-full max-w-md">
      <CardHeader className="items-start justify-between">
        <div className="min-w-0">
          <h3 className="font-semibold">payload-cache</h3>
          <p className="text-sm text-muted">New release</p>
        </div>
        <Badge color="success" variant="dot">
          CI passing
        </Badge>
      </CardHeader>
      <CardBody className="gap-4">
        <div className="grid gap-3 sm:grid-cols-2">
          <Input variant="faded" label="Version" value={version} onValueChange={setVersion} />
          <Select variant="faded" label="Tag" value={tag} onValueChange={setTag}>
            <SelectItem value="latest">latest</SelectItem>
            <SelectItem value="next">next</SelectItem>
          </Select>
        </div>
        <Switch defaultSelected>Publish with provenance</Switch>
      </CardBody>
      <CardFooter className="justify-end">
        <Button color="primary" variant="shadow" isLoading={isPublishing} isDisabled={!version || !tag} onClick={publish}>
          {isPublishing ? "Publishing" : "Publish"}
        </Button>
      </CardFooter>
    </Card>
  );
}

What is inside#

These docs run to 74 pages in 11 families. The first 7, under Getting started, are guides like this one; the other 10 families cover the components:

  • Actions, 2 pages. Buttons and grouped toggles.
  • Inputs, 15 pages. Fields that work controlled and uncontrolled, inside <Form> and with react-hook-form.
  • Dates and times, 4 pages. ISO strings in and out, Intl for names and order, no date library.
  • Overlays, 10 pages. Nested in each other, opened by touch and by keyboard, tested in every engine.
  • Navigation, 6 pages. Getting around an app: menus, sidebars, steps and pages.
  • Data display, 10 pages. Tables, trees, long lists and the small pieces inside them.
  • Feedback, 6 pages. Status, progress and the moments before content arrives.
  • Layout, 4 pages. Surfaces and structure.
  • Charts, 6 pages. cennge-ui/charts: own SVG, no chart library, palette checked for colour blindness.
  • Map, 4 pages. cennge-ui/map: MapLibre, loaded only when a map mounts, themed by your tokens.

Everything above comes from the main entry, cennge-ui, except charts and maps, which have their own: cennge-ui/charts and cennge-ui/map. In the ESM build each component is a chunk of its own, so a page pays only for what it imports; every component's page gives the gzip size of its import in its field marks.

What it is built on#

The package has four dependencies:

  • radix-ui, for behaviour, focus management and keyboard handling. Where Radix has no primitive (the calendar, the tree, the ⌘K palette), the keyboard follows the WAI-ARIA Authoring Practices.
  • tailwind-variants, tailwind-merge and clsx, for class names: each component's variants, and your className winning over them.

It also expects what your app already has: react and react-dom 17, 18 or 19, and tailwindcss 4. maplibre-gl 6 is needed only for maps, and is loaded when the first map mounts.

What it leaves out#

  • No animation library such as framer-motion. Motion is plain CSS, keyframes and transitions, and respects prefers-reduced-motion.
  • No CSS-in-JS. Styles are Tailwind classes and the theme is CSS variables, so retheming is a change to your stylesheet, with nothing to configure in JavaScript.
  • No chart library. Charts are drawn in SVG by cennge-ui itself, with a palette checked for colour blindness.
  • No date library. Dates are ISO strings such as "2026-10-19"; names of months and days, the first day of the week and the order of a typed date come from Intl.
  • No carousel or virtualization package either: the Carousel runs on native scroll snapping, and long lists use the library's own virtualizer.

Fewer packages underneath means fewer upstream releases that can break a component you did not touch.

How it is checked#

  • Unit tests with Vitest and Testing Library, for logic, roles and labels.
  • End-to-end tests with Playwright in Chromium, Firefox and WebKit, on desktop and phone viewports: real keyboard, mouse, touch, file choosers and drag events.
  • axe-core against WCAG 2.2 AA on every fixture, in the dark and the light theme.
  • Any error or warning in the browser console fails a test.
  • A Next.js App Router app, built on the packed package, renders the whole playground on the server and hydrates it in every browser.
  • CI runs the unit tests on React 17 and 18 as well; the other jobs run React 19.

Where a component has end-to-end specs, the field marks on its page link to them.

Status#

cennge-ui is in early development, at version 0.x, under the MIT licence. Every release lists its changes in the changelog: read it before you update.

Next#

  • Installation: the package, two CSS imports, and a first component.
  • Theming: tokens, surfaces, colour roles and the accent presets.
  • Dark mode: the stored theme on the page before the first paint.