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';

PackageWhy
@carabine/uiThe components
motionPeer. Every entrance, exit and gesture runs on it
react · react-domPeer. 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

PageWhat it answers
IntroductionWhat the library is, the rules it is built on, and what it refuses to do
The motion standardOne easing, the duration table, and why exits are faster than entrances
Any componentHow it works, then the API, then the refusals