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.
| Category | Components |
|---|---|
| Overlay | Dialog · Popover · Tooltip · Menu · Drawer · Command palette |
| Input | Text field · OTP input · Color picker · Switch · Slider · Select |
| Action | Button |
| Navigation | Tabs · Segmented control · Accordion |
| Feedback | Toast · Skeleton · Progress |
| List | Hover 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.
| Rule | Because |
|---|---|
| Movement is Motion, style is Tailwind | Neither 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 count | offsetWidth, never index × width — and never a bounding rect on anything animated, because a rect includes the transform. |
| No dark: variants | They 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 it | Glass 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 thing | Hover 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 structural | Live 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 APIEach 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.