Overview
Getting started
Install Efni in your project or run the repository to develop the library and documentation site.
Which one do you want?
Running the repo gives you this site and the components' source for development.
Installing the package gives your app compiled components, styles, and type declarations.
A. Run this repo
The workspace holds the library and this documentation site. The site consumes the library's source directly, so there is no build step: change a component, save, and it shows up.
git clone https://github.com/ravnhq/efni-ds.git
cd efni-ds
pnpm install
pnpm dev # docs on :3000, against the library's sourcepackages/ui is the library — one file per component, plus the CSS design owns in packages/ui/styles. apps/docs is this site.
pnpm build # the static site
pnpm test # component contract suite + docs checks
pnpm test:artifact # builds the library and checks what npm would receive
pnpm lint
pnpm typecheckB. Install it into your project
Efni is a public package on npm, under Ravn's @ravnhq org.
pnpm add @ravnhq/efniThe public package ships compiled JavaScript and type declarations.
B1. Replace your CSS entry
Replace the contents of app/globals.css with this one line. Do not add it alongside what is already there.
/* app/globals.css */
@import "@ravnhq/efni/styles.css";It brings Tailwind, the tokens, the shadcn bridge, and an @source pointing at the package — Tailwind v4 does not scan node_modules, so without it none of the utility classes the components rely on would be generated.
Replacing is the part that bites. create-next-app writes a globals.css defining --background and --foreground — the same names the shadcn bridge maps onto Efni tokens — plus body { font-family: Arial, … } and its own font mapping. Keep them and they win: the page stays light with data-theme="dark" set, and every component renders in Arial. Nothing errors, which is what makes it easy to miss.
B2. Fonts and theme attributes
The tokens map --font-display, --font-sans and --font-mono onto three variables you define, so without them you get no typography. data-theme is required; data-tool only if you want an accent other than the default.
import { Hanken_Grotesk, Inter, JetBrains_Mono } from "next/font/google";
const display = Hanken_Grotesk({ variable: "--font-hanken-grotesk", subsets: ["latin"] });
const body = Inter({ variable: "--font-inter", subsets: ["latin"] });
const mono = JetBrains_Mono({ variable: "--font-jetbrains-mono", subsets: ["latin"] });
<html data-theme="light" className={`${display.variable} ${body.variable} ${mono.variable}`}>Switching them is yours to wire — the library reads the attributes and ships no provider of its own, which is why it works in any React app rather than only in Next. At its simplest that is one line:
document.documentElement.setAttribute("data-theme", "dark")For a real toggle that survives reload and respects the system preference, next-themes with attribute="data-theme" is what this site uses.
B3. Import a component
One import per component. There is no barrel export, so you only pay for what you use.
import { Button } from "@ravnhq/efni/button";
import { Typography } from "@ravnhq/efni/typography";
<Typography variant="h1">Mission MB-4472</Typography>
<Button>Save</Button>react, react-dom and tailwindcss come with create-next-app. lucide-react does not, and sixteen components import it, so install it even if you never touch Icon. Four more are optional, needed only by the component that uses them.
pnpm add lucide-react
# only if you use the component on the right
pnpm add react-hook-form # Form
pnpm add "@tanstack/react-table@^8" # DataTable
pnpm add sonner # toast()
pnpm add react-day-picker # CalendarThe ^8 is not optional: plain pnpm add @tanstack/react-table installs v9, which renamed getCoreRowModel and useReactTable, and the build fails. And if you ever see TypeScript errors inside @ravnhq/efni/dist, you are missing a peer — our declarations reference the peers' types, so an absent one surfaces in our files. Install whatever the first error names.
Use semantic tokens, never primitives
Prefer shadcn's mapped utilities — bg-background, text-foreground, bg-primary. For anything without a shadcn equivalent, reach for the Efni CSS variable directly.
// ✅ semantic
<div className="bg-card text-foreground border border-border" />
<div style={{ color: "var(--color-text-brand)" }} />
// ❌ primitive or hex
<div className="bg-[oklch(0.655_0.13_80)]" />