Getting started

Introduction

Twenty React components, styled by default and moving by default. One easing curve, both themes written out, every prop optional.

What this is

A component library you install and use, not one you assemble. Every component arrives with its look, its movement and its accessibility already decided — <Toaster /> with no props at all is the version we would ship.

React 19, Tailwind 4 for style, Motion for movement, TypeScript throughout. Peers are react, react-dom and motion; dependencies are lucide-react and zustand. Nothing else, and nothing new without a reason that survives being questioned.

CategoryComponents
OverlayDialog · Popover · Tooltip · Menu · Drawer · Command palette
InputText field · OTP input · Color picker · Switch · Slider · Select
ActionButton
NavigationTabs · Segmented control · Accordion
FeedbackToast · Skeleton · Progress
ListHover list

Every component moves the same way or the library has no feel. That is not a style guide — it is the reason this exists rather than being a folder of copied snippets.

The bet

Headless libraries won the argument about logic. They did not win the argument about feel, and feel is what people actually notice: the way a menu grows out of the corner nearest the thing that opened it, the way a sheet gives when you pull it the wrong way, the way a thumb stays exactly under a finger instead of springing towards it.

So this library ships opinions, and makes them replaceable where it matters. Structure stays in utilities. Anything that identifies one state from another — a toast's tones, a button's variants, a switch's skin — is an object of plain CSS colour strings merged over the defaults, with the key set left open so a consumer can invent a state. A consumer cannot override bg-green-500 buried in a component.

The rules

These are load-bearing. Every one of them came from getting it wrong first, and each has a comment sitting exactly where somebody would reintroduce it.

RuleBecause
Movement is Motion, style is TailwindNeither leaks. The one exception is press feedback, which is a CSS transition so it cannot wait behind whatever else has the main thread.
Measure, do not countoffsetWidth, never index × width — and never a bounding rect on anything animated, because a rect includes the transform.
No dark: variantsThey resolve from the operating system, so they fire on a light-themed page running on a dark-mode machine. The theme is a prop.
Translucent only with a blur behind itGlass with no backdrop-filter shows whatever the page is made of, sharply. A hole is opaque because you are reading against it.
Two surfaces never claim the same thingHover and keyboard focus are the same question — this is the one you are on — and answering it twice leaves a component pointing at two rows.
Accessibility is structuralLive regions mount before the content they announce. Icon-only controls have labels, and every label is a prop because it will be translated.

What it refuses

Every component's page ends with this heading. The refusals are the API as much as the props are, and they are written down so nobody has to guess whether something is missing or declined.

  • No component imports another

    Duplicating a helper is cheaper than coupling. There are four copies of the same placement arithmetic in this library, and that is still cheaper than the edge that lets one component's bug become four components' regression. It also keeps copy-paste and a shadcn-style registry possible later.

  • No compound APIs

    No Menu.Root / Menu.Item. Items are data — an array a caller can build, filter and reorder — because that is what a list of commands actually is in an application.

  • No headless mode

    A styled library with data-driven colours already covers the case headless exists for. Shipping both means every component has two surfaces to keep correct, and one of them is always behind.

The shape of a component

All twenty are the same flat folder, and knowing the shape means knowing where to look in any of them.

src/toast/
  index.ts          the component's public surface
  Toaster.tsx       viewport: placement, stacking, gestures
  ToastCard.tsx     one card
  store.ts          state, and the imperative API
  tones.tsx         per-state look, and the defaults
  toast.css         what utilities cannot express
  toast.test.tsx    colocated
  README.md         how it works, then the API

Each one is importable on its own — @carabine/ui/toast — so a project that wants one component pays for one component. There are 20 of those entry points, plus @carabine/ui/cn and the stylesheet.