Install the CSS-only Frutjam UI library with Tailwind CSS v4, UnoCSS, or no build step at all. 65+ accessible components, zero JavaScript, WCAG AA. Works with Django, HTMX, Laravel, React, and any stack.
Install
npm i -D frutjam |
Then pick the line below that matches your setup. Every one gives you the same components.
| Your setup | What to add | Ships |
|---|---|---|
| Tailwind CSS v4 | @plugin "frutjam"; | only what you use |
| UnoCSS | presetFrutjam() | only what you use |
| Vite, Astro, Rails, plain CSS | import 'frutjam/css' | all components, 30 KB gzipped |
| No build step | a <link> tag | all components |
Tailwind CSS v4
You need a working Tailwind CSS project, plus Node and npm. Frutjam requires Tailwind 4.
Add Frutjam to your input.css with the @plugin directive:
1 2 | @import "tailwindcss"; @plugin "frutjam"; |
That is the whole setup. All 65+ components are available immediately, and Tailwind ships only the ones your markup uses.
UnoCSS
Frutjam ships a preset, so UnoCSS generates only the components you use, the same way the Tailwind plugin does.
1 2 3 4 5 6 7 | // uno.config.js import { defineConfig, presetWind4 } from 'unocss'; import { presetFrutjam } from 'frutjam/unocss'; export default defineConfig({ presets: [presetWind4(), presetFrutjam()], }); |
Every class works as it does under Tailwind, and so do variants:
1 2 3 | <button class="btn btn-error btn-xl">Delete</button> <button class="md:btn-error">Red from the medium breakpoint up</button> <div class="hover:card-primary">Fills on hover</div> |
Add presetAttributify() and the component name becomes an attribute, with each value treated as a variant of it:
1 2 | <button btn="error xl">Delete</button> <button btn="md:(error xl)">Red and large from the medium breakpoint up</button> |
The second line needs transformerVariantGroup() for the bracket syntax. Note that an attribute like btn is not valid HTML, so if you validate your markup, use the class form or UnoCSS's un- prefix.
Options
| Option | Default | Purpose |
|---|---|---|
preflight | true | Emits Frutjam's tokens, reset and themes. Set to false if you already import frutjam/css/base yourself. |
prefix | '' | Matches the Tailwind plugin's prefix, so 'fj' gives you fj-btn and fj-btn="error". |
layer | 'frutjam' | The cascade layer the components are emitted into. It sorts below utilities, so a utility always wins over a component whatever order the presets are in. |
The link component needs one line of config
UnoCSS treats link as a variant, for the CSS :link pseudo-class, and its default separators include -. So link-2xl is read as "2xl, when unvisited" rather than as a class name, and nothing is generated. It affects only this component, and only the link-* modifiers.
Either tell UnoCSS to split on colons alone:
1 2 3 4 | export default defineConfig({ separators: [':'], presets: [presetWind4(), presetFrutjam()], }); |
Or use a prefix, which puts something other than link at the front of the name:
presets: [presetWind4(), presetFrutjam({ prefix: 'fj' })], // fj-link-2xl |
Every other component is unaffected. link is the only one of the 57 whose name collides with a pseudo-class.
No utility engine
Frutjam is built with Tailwind, but the components compile to ordinary CSS that reads custom properties. If your project uses no utility engine at all, import the stylesheet instead of the plugin:
import 'frutjam/css'; |
That is the whole setup. Every component class works, themes switch with data-theme as usual, and nothing else is required. It is 30 KB gzipped and carries the base styles, both themes and all 65+ components.
Pull in only what you need instead:
1 2 3 4 | import 'frutjam/css/base'; // tokens and reset, always needed import 'frutjam/css/components/button'; import 'frutjam/css/components/card'; import 'frutjam/css/themes/darkberry'; // optional |
Or with no build step at all:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/frutjam@2/dist/frutjam.components.css"> |
No build step
Drop a tag in and start writing markup. Use this for a prototype or a page you do not build, not for production, since it ships every component.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/frutjam@2/dist/frutjam.components.css"> |
To experiment with Tailwind's utilities on the same page, add Tailwind's browser build alongside it:
1 2 | <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/frutjam@2/dist/frutjam.min.css"> <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script> |
JavaScript and React: optional, not required
Frutjam is native-first and CSS-first. The vast majority of components: buttons, badges, cards, forms, alerts, and more: work with pure HTML and CSS, zero JavaScript needed. Native HTML elements like <dialog>, <details>, and <input type="checkbox"> handle interactivity where browsers already support it. All components are WCAG AA accessible out of the box and work with Django, HTMX, Laravel, React, Next.js, and any stack.
A small number of components have no native HTML equivalent (toast notifications, combobox filtering, carousel controls). For those cases only, Frutjam ships lightweight optional helpers.
Vanilla JS: frutjam/js
Framework-agnostic ES module helpers for the components that need them. Attach to any DOM element, no framework required.
1 2 3 4 5 6 | import { createModal } from 'frutjam/js' const modal = createModal(document.getElementById('myModal')) modal.open() // calls showModal() modal.close() // calls close() modal.toggle() |
React Hooks: frutjam/react
Thin React wrappers around the same native DOM calls. Each hook returns a ref you attach to the element and methods to control it, no extra dependencies beyond React itself.
1 2 3 4 5 6 7 8 9 10 11 | import { useModal } from 'frutjam/react' export default function App() { const modal = useModal() return ( <> <button onClick={modal.open}>Open</button> <dialog ref={modal.ref} className="modal">...</dialog> </> ) } |
Available for: useModal, useDrawer, useCollapsible, useTooltip, useTabs, useToast, useCarousel, useCombobox.
Next Steps
Once installed, try the button component to see Frutjam in action, or explore the full component library.
AI Editor Integration
Using Claude Code, Cursor, or another AI editor?
Cherry MCP brings Frutjam's full component library directly into your AI editor's context, accurate class names, live docs, no copy-pasting.
Learn about Cherry MCP →Starter project
Building with Django?
A working Django 6 project with Frutjam already wired up, accounts, a dashboard, themes and a markdown-backed CRUD example. Clone it and start building.
Get the Django starter →Want to understand the thinking behind Frutjam's CSS-first approach? Read why we built it this way →