Packages
Playa
ajo-ui-playa: themed components and a build-time UnoCSS preset.
ajo-ui-playa is a themed component library for Ajo. Its families are the accessible ajo-ui components with Playa’s visual design applied, and its styles come from a build-time UnoCSS preset rather than a stylesheet you import. The official starter builds its pages and forms with it.
The package has two deliberately separate surfaces. The root exports only playa(), the UnoCSS preset your build configuration reads. Each of the 62 family subpaths, such as ajo-ui-playa/button, exports runtime components. There is no root barrel for the components.
Install
pnpm add ajo@0.1.35 ajo-ui-playa@0.1.2
pnpm add -D unocss@66.7.2ajo ^0.1.35 and unocss are peers, and the UnoCSS peer is exact: this release is verified against UnoCSS 66.7.2 with Vite 8.0.16. UnoCSS is build tooling, not a browser dependency. ajo-ui arrives as a dependency of Playa; a themed application does not declare or import it.
Set up UnoCSS
Add the preset to the application’s UnoCSS configuration.
uno.config.ts import { playa } from 'ajo-ui-playa' import { defineConfig } from 'unocss' export default defineConfig({ presets: [playa()] })Register the UnoCSS Vite plugin next to Kit’s, and let Kit load the generated stylesheet. With
css, Kit includesvirtual:uno.cssin the page before hydration. This is the starter’s configuration:vite.config.ts import { defineConfig } from 'vite' import { kit } from 'ajo-kit/vite' import unocss from 'unocss/vite' export default defineConfig({ optimizeDeps: { exclude: ['ajo-kit/client'] }, plugins: [...kit({ css: ['virtual:uno.css'] }), unocss()], })
Without Kit, add unocss() to the Vite plugins and import the stylesheet from the application entry with import 'virtual:uno.css'. Playa publishes no precompiled CSS, and there is no ajo-ui-playa/vite wrapper.
Use the components
Import each family from its subpath. Family imports are side-effect-free and tree-shakeable.
import Button from 'ajo-ui-playa/button'
import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ajo-ui-playa/card'
type Args = { title: string; text: string; created: string }
export default ({ title, text, created }: Args) => (
<Card as="article">
<CardHeader>
<CardTitle role="heading" aria-level={2}>{title}</CardTitle>
<CardDescription>{created}</CardDescription>
<CardAction>
<Button variant="ghost" size="icon-sm" aria-label="Pin note">
<span class="i-lucide-pin" aria-hidden="true" />
</Button>
</CardAction>
</CardHeader>
<CardContent>
<p>{text}</p>
</CardContent>
<CardFooter class="gap-2">
<Button size="sm">Edit</Button>
<Button size="sm" variant="outline" as="a" href="/notes">All notes</Button>
</CardFooter>
</Card>
)Buttontakes avariant(default,secondary,outline,ghost,muted-ghost,link,danger,danger-ghost) and asize(default,xs,sm,lg,icon,icon-xs,icon-sm,icon-lg,none). Withas="a"and anhrefit renders a link.Cardrenders adivby default, or anarticle,section,formorathroughas, withsizedefaultorsm.classalways styles the visible root of a component, so utilities you add, such asgap-2above, join the theme’s classes there.- Families that export reusable recipes name them
…Variants.buttonVariants({ variant, size })returns the class list of a button for another element.
Catalog
| Group | Family subpaths |
|---|---|
| Actions and status | alert, alert-dialog, button, button-group, chip, marker, spinner |
| Content and layout | aspect-ratio, attachment, breadcrumb, bubble, card, empty, item, kbd, label, pagination, scroll-area, separator, skeleton, table, typography |
| Inputs and selection | checkbox, checkbox-group, field, input, input-date, input-group, input-otp, radio-group, select, slider, switch, textarea, toggle, toggle-group |
| Navigation and overlays | accordion, collapsible, command, context-menu, dialog, direction, drawer, menu, menubar, navigation-menu, popover, sidebar, tabs, toast, toolbar, tooltip |
| Data and media | avatar, calendar, carousel, chart, data-table, message, message-scroller, progress, resizable, virtual-list |
Families that wrap an ajo-ui family render its components underneath, so ajo-ui’s keyboard behavior, ARIA wiring, state arguments and state attributes carry over.
What the preset configures
playa() takes no options. It sets up:
- Wind4. UnoCSS’s Wind4 preset provides the utility classes, such as
flex,gap-2androunded-lg. - Lucide icons. The Icons preset with the Lucide collection, so
i-lucide-pinrenders that icon as an inline block. - Tokens. Semantic colors:
backgroundandforeground;card,popover,primary,secondary,muted,accent,danger,success,warningandinfo, each with a-foregroundpair; andborder,inputandring. A radius scale derived from one--radiusvariable, andxsandlgshadows. - Preflights. The light values on
:rootand the dark values under a.darkclass, keyframes, and component-level rules for popups, charts, drawers, toasts, scrollbars and overflow edge fades, with reduced-motion variants. - Variants and rules.
aria-invalid:,has-aria-invalid:andpointer-coarse:variants; rules such asscrollbar-none,scroll-fade-x,shimmer, and theanimate-inandanimate-outfamilies. - Shortcuts.
edgeandedge-inputhairlines, theglass-chrome,glassandglass-overlaysurfaces, and theplaya-*component recipes.
To switch to dark mode, put the dark class on an ancestor such as <html>. The variables, global rules, keyframes and preflights are emitted in every build, whichever families you import.
How classes are found
UnoCSS generates only the classes it sees. Each published Playa module carries UnoCSS’s @unocss-include marker, so the families your application imports pass through the build and contribute their classes; families you never import add nothing. Classes you write in your own source are found there. One stylesheet results, and neither UnoCSS nor the icon data reaches client JavaScript.
A class that exists only at runtime cannot be found. Write every class as a complete literal, and put names you compute, such as an icon chosen from data, in the application’s safelist. Application-wide combinations belong in uno.config.ts as your own shortcuts:
import { playa } from 'ajo-ui-playa'
import { defineConfig } from 'unocss'
export default defineConfig({
presets: [playa()],
shortcuts: {
'note-grid': 'grid gap-4 sm:grid-cols-2',
},
// Rendered as `i-lucide-${note.icon}`, so no source file contains them.
safelist: ['i-lucide-star', 'i-lucide-archive', 'i-lucide-lightbulb'],
})- Unstyled UIThe accessible families underneath Playa.
- Build and configurationThe Kit Vite plugin and its options.
- Quick startCreate a Kit application to style.
Source of truth: ajo-ui-playa README