Getting started
Get started
Install it, import the stylesheet, render a component. There is no provider to mount, no config file to write, and no theme object to build.
Install
Three packages, and two of them you already have. motion is a peer rather than a dependency so your app owns the version — this library is not the only thing that will want to animate something.
$ pnpm add @carabine/ui motion
> import '@carabine/ui/styles.css';
| Package | Why |
|---|---|
| @carabine/ui | The components |
| motion | Peer. Every entrance, exit and gesture runs on it |
| react · react-dom | Peer. 18 or 19 |
Its own dependencies are lucide-react and zustand, and nothing else. Nothing new goes in without a reason that survives being questioned.
The stylesheet
This is the one line people leave out, and leaving it out is the one way to install this library and see nothing at all.
The components are styled with Tailwind utilities, compiled ahead of time into a single file that ships in the package. Import it once, wherever your app's global CSS is imported.
// app/layout.tsx — or main.tsx, or wherever your global CSS goes
import '@carabine/ui/styles.css';It carries no preflight. A library has no business resetting its host's styles, so only the theme tokens and the utilities the components actually use are emitted — your own reset, your own base styles and your own utilities are untouched.
Your first component
Every prop is optional and every default is one we would ship. <Toaster /> with nothing on it is the version in the screenshots.
import { Toaster, toast, Button } from '@carabine/ui';
import '@carabine/ui/styles.css';
export default function App() {
return (
<>
{/* Mount the viewport once, high in the tree. */}
<Toaster />
<Button onClick={() => toast.success('Saved')}>Save changes</Button>
</>
);
}The viewport goes in once, near the root — not beside the thing that fires a toast. Its live regions have to exist before the content they announce, so a viewport mounted at the moment a toast fires announces nothing.
Themes
There are two, they are both written out in full, and the theme is a prop.
<Toaster theme="dark" />
<Button theme="light">Save</Button>Why not a `dark:` variant
Because dark: resolves from the operating system. A light-themed page running on a dark-mode laptop would paint dark chrome inside it — the component would be answering a question the page had already answered differently.
Most apps hold the theme somewhere already. Read it once and hand it down; there is no provider here to fight with the one you have.
const theme = useYourTheme(); // whatever you already use
<Select theme={theme} items={countries} />
<Slider theme={theme} label="Volume" />Importing one component
Every component is its own entry point, so a project that wants one component pays for one component.
import { Toast } from '@carabine/ui/toast';
import { Select } from '@carabine/ui/select';
import { cn } from '@carabine/ui/cn';The barrel — @carabine/ui — is there for convenience and tree shakes correctly under any modern bundler. Reach for the deep paths when you want the guarantee rather than the behaviour.
If you use Tailwind
You have a second option, and it deduplicates: point your own build at the package instead of importing the compiled file, and the utilities the components use are merged with yours.
@import 'tailwindcss';
@source '../node_modules/@carabine/ui/dist';Either path works. Importing the stylesheet is the one most people take, so it is the one this site takes too — anything broken about it is broken here first.
Where to go next
| Page | What it answers |
|---|---|
| Introduction | What the library is, the rules it is built on, and what it refuses to do |
| The motion standard | One easing, the duration table, and why exits are faster than entrances |
| Any component | How it works, then the API, then the refusals |