# GreedUI Agent-first design system: Base UI primitives + Tailwind v4 tokens + Motion. Copy-in source, zero runtime network calls. Install any item: npx shadcn@latest add https://greedui.com/r/.json (or copy the file; imports are @/components/ui/*, @/lib/*). Tokens: bg-canvas|surface|sunken|tint, text-ink|ink-2|ink-3, border-line|line-strong, brand|brand-ink, positive|warning|danger|info. Props: size=sm|md|lg, tone=neutral|brand|danger (where relevant). Compose with `render`, never `asChild`. className can be a Base UI state function. Motion: changing text/numbers go through , size changes through , groups of hoverable items share one gliding highlight (HoverTrail / useHoverTrail, HighlightTrail in popups). Effects: add fx-halftone next to anim-pop|anim-rise|anim-fade for the dotted popup dissolve. Feature switches: , or setFeature/useFeature from @/lib/features. Full API (every export + Props type, one fetch): /llms-full.txt ## Design language (keep new UI consistent with it) Interfaces that answer back: actions preview their consequence (hint, inline line, rolling label) and confirm what happened. Fewer containers: hairlines and whitespace, never card-in-card; only floating things get a surface + shadow-pop. Motion is buttery and instant-feeling: ease-smooth, no wobble, nothing pops in. Changing text = , size changes = , hover groups = HoverTrail. Reserve space so rolling text never shifts layout. Warm tokens only, brand color for the one primary action and small signals, dots/halftone/pixel accents in small doses. 24px+ hit areas, keyboard, AA contrast, Reduce Motion respected by default. ## Versions (current release: 1.0.10) Each item below shows (vX.Y.Z), the release it last changed in. Installed files carry the same number in their header as `@version`. To check a project: compare each installed file's @version with this list. If ours is newer, it changed: check the item's docs page, then re-run the install command (the CLI asks before overwriting, so keep any local edits). ## Before you install, ask the user (then respect the answers) 1. Theme: a hue or preset (ember, iris, moss, tide, mono, sakura), or the default? The Studio at /themes exports one CSS file. 2. Motion: keep it (default)? If no, leave out theme/greedui/motion.css and set . 3. Reduce Motion: respect the OS setting (default, recommended)? Only if they explicitly want full motion for everyone, set . 4. Effects (pixel wipe, halftone dissolve), squircle corners, pixel details, texture: keep each (default) or drop it. Dropping one = delete its @import line in greedui.css (effects, shape, pixel, texture) and set data-ui-="off" for motion/effects. Don't add fallbacks for parts they dropped. 5. Icons: the pixel set (default) or a pack (hugeicons, phosphor, tabler, remix, iconoir, pixelart)? Install icons- over components/ui/icons.tsx. If the user says "just pick", use the defaults: everything on, Reduce Motion respected, pixel icons. ## Components - accordion (v1.0.0): Hairline-divided disclosure rows, no box. or compound AccordionRoot/Item/Trigger/Panel. e.g. - activity (v1.0.0): "What just happened" feed: timeline rows with pixel time, actor, action, target, tone node, undo affordance (onUndo, undone). New rows slide in via CSS. one-liner or ActivityList + ActivityItem parts. e.g. - alert-dialog (v1.0.0): Confirm-before-consequence dialog. or parts AlertDialogRoot/Trigger/Portal/Backdrop/Viewport/Popup/Title/Description/Close/Footer. e.g. Delete} title="Delete 3 files?" description="They skip the trash." confirm="Delete 3 files" onConfirm={remove} /> - animated-number (v1.0.1): Number whose digits roll like an odometer (only changed digits move: up when it grows, down when it shrinks, staggered). value, from (mount start), format=Intl.NumberFormatOptions, locales, stagger (ms), pixel font, spring (counts through every value instead, duration in s). Tabular nums. e.g. · - autocomplete (v1.0.0): Free-text input with suggestions (search, tags, commands). . Compound Autocomplete* parts. e.g. } placeholder="Search tags" items={["feature", "fix", "docs"]} /> - avatar (v1.0.0): Profile image with pixel-font initials fallback (tone hashed from name). size=sm|md|lg, shape=round|pixel. AvatarGroup stacks, spreads on hover, max → "+N". e.g. · … - badge (v1.0.0): Static status label with stepped pixel corners. tone=neutral|brand|positive|warning|danger|info, variant=soft|solid|dot, size=sm|md, pulse (live dot), pixel (Geist Pixel caps). e.g. Live · Deploying - button (v1.0.0): Pressable action. variant=solid|soft|ghost|outline, tone=neutral|brand|danger, size=sm|md|lg, square for icon-only, loading (crossfades to a spinner), morph (label rolls + width glides when children change). e.g. · - chart-bar-list (v1.0.0): Ranked list that reads like a sentence: label, a dotted leader that fills to the value, share %, value. Hover trail across rows, numbers tumble on change. data=[{label,value,href?,hint?,tone?}], total, format, limit, sort, rank, tone, size=sm|md. e.g. - chart-core (v1.0.6): Shared plumbing for chart-*: useChartSize (ResizeObserver), useFormat, useCursor + ChartReadout (hover index that re-renders only the readout), ChartTable (sr-only data), DitherFill (Bayer dot dither), chartTones. e.g. const [ref, { w, h }] = useChartSize() · - chart-delta (v1.0.0): Before/after slope chart: one line per item, labels on both ends nudged apart by their real height so they never collide (long ones wrap to 2 lines, then clamp with a Tooltip), pixel % badge on the right, hover spotlights a line. data=[{label,before,after}], labels=[before,after], format, invert (down is good), tone. e.g. - chart-dither-area (v1.0.0): Area chart filled with an ordered dot dither that thins toward the baseline, crisp line on top, gliding crosshair + Tumbler readout, arrow keys. data, labels, compare (dashed), label, format, tone, curve, min, max. e.g. - chart-dots (v1.0.9): Countable dot-matrix bars: rounded-square cells fill in, a dithered cell shows the remainder, over-budget cells turn danger. data=[{label,value,max?,tone?}], unit (value per cell, auto), max, maxCells, orientation=horizontal|vertical, size=sm|md|lg, tone, format, unitLabel. Hover trail across rows. e.g. - chart-heat (v1.0.9): Calendar heat grid: one rounded cell per day, 5 tonal steps of the brand hue, gliding outline + Tumbler readout, arrow keys. Shows the latest weeks that fit. data (daily values), end (date of the last value), unit, format, thresholds, tone, minCell. e.g. - chart-pulse (v1.0.0): Live heartbeat strip: one small tick per event or interval, new ticks slide in from the right and old ones fade out on the left. Append to data, don't mutate. data=(number|{value,tone?,label?})[], label, unit, format, tone, size=sm|md|lg, align=center|bottom, max, live. e.g. · data={[...prev.slice(-59), { value: 1, tone: "brand", label: "deploy" }]} - chart-ring-dots (v1.0.0): Radial progress drawn like a watch dial: minute dots with baton markers, a hand tip that sweeps to the value, Tumbler number in the middle. value, max, dots (60), label, format, tone, size=sm|md|lg, numerals, children (custom center). e.g. - chart-waffle (v1.0.0): 10x10 waffle for parts of a whole: one cell = 1%, cells roll in row by row, hovering a legend row or cell spotlights that part with a Tumbler readout. data=[{label,value,color?}], total (leftover cells stay empty), parts told apart by early-Mac fill patterns in one tone, format, size=sm|md|lg, legend, tone. e.g. - checkbox-group (v1.0.0): Shared-state checkbox list (Base UI CheckboxGroup). ; select-all goes indeterminate, children hang off a hairline. Part: CheckboxGroupRoot. e.g. - checkbox (v1.0.9): Checkbox (Base UI Checkbox): empty boxes sit inset, checked ones fill in raised while the pixel check draws in with a tiny overshoot; unchecking eases it all back. size=sm|md|lg, tone=brand|neutral|positive|danger, indeterminate, label + description for the common row. Parts: CheckboxRoot, CheckboxIndicator. e.g. - collapsible (v1.0.0): Single show/hide region with an interruptible height transition. … or compound CollapsibleRoot/Trigger/Panel. e.g. … - combobox (v1.0.9): Filterable select (values are option ids). . Async: filter={null} + onInputValueChange. Compound Combobox* parts. e.g. - command (v1.0.0): ⌘K palette (Dialog + Autocomplete): grouped items {id,label,hint,icon,kbd,keywords,tone,onSelect}, highlighted item previews its consequence in the footer, recent items, empty state, shortcut. useCommandShortcut(key) → [open, setOpen]. Parts: CommandRoot, CommandPopup, CommandSearch, CommandInput, CommandList, CommandGroup, CommandItem, CommandEmpty, CommandFooter, CommandKbd. e.g. - context-menu (v1.0.0): Right-click / long-press menu at the pointer. area; items are the menu vocabulary (ContextMenuItem icon|description|shortcut|tone, checkbox, radio, ContextMenuSub). e.g. CopyDelete}>
Right-click me
- dialog (v1.0.0): Modal dialog. or parts DialogRoot/Trigger/Portal/Backdrop/Viewport/Popup/Title/Description/Close/CloseButton/Body/Footer. Nested dialogs recede. e.g. Rename} title="Rename project" footer={}>Save}>… - drawer (v1.0.0): Swipe-to-dismiss panel. or parts DrawerRoot(side)/Trigger/Portal/Backdrop/Viewport/Popup/Grip/Content/Title/Description/Close/CloseButton/Footer/Provider/Indent. e.g. Filters} title="Filters" footer={}>Show 24 results}>… - empty (v1.0.0): Empty state, no card: pixel icon in a stepped-corner tile on a dot halo, label, title, description, actions. align=center|start. Compound EmptyRoot/Icon/Title/Description/Actions. e.g. } label="0 projects" title="Nothing here yet" description="Projects you create show up here." actions={} /> - field (v1.0.0): Label + control + description + inline error (Base UI Field). ; error replaces the description in place with a short fade. Parts: FieldRoot, FieldLabel, FieldControl, FieldDescription, FieldError, FieldItem, FieldValidity. e.g. - fieldset (v1.0.0): Groups related fields under one legend (Base UI Fieldset).
; hairline under the heading, whitespace between fields. Parts: FieldsetRoot, FieldsetLegend. e.g.
…
- flow-sheet (v1.0.5): Apple Pay style multi-step sheet: one surface that glides its width and height per step (Morph axis="both"), content slides the way you're going. steps=[{id,label,width,render(flow),run,back}] (run = auto step, moves on when it resolves, goes back with flow.error if it throws), step/defaultStep/onStepChange, drawer (bottom sheet via Drawer) + trigger/open/onOpenChange. Esc goes back. Parts: FlowSheetHeader, FlowSheetRow, FlowSheetFooter, FlowSheetProcessing, FlowSheetDone, useFlowSheet. e.g. }, { id: "wait", run: pay, render: () => }, { id: "done", render: () => }]} /> - form (v1.0.0): Native form with consolidated validation (Base UI Form): errors={{name: msg}} from the server, validationMode=onSubmit|onBlur|onChange, onFormSubmit(values). FormActions = hairline-topped button row. e.g.
- hand-underline (v1.0.1): Wraps a few words and draws a hand-drawn mark on hover/focus, or while active (controlled). shape=underline|double|circle|strike, tone=brand|neutral|positive|danger, delay (ms). One SVG stroke per mark, CSS transition only. handDraw = the shared draw classes. e.g. hover me · saved - highlight-trail (v1.0.0): One highlight that glides to whichever child matches `selector` (default [data-highlighted], so it follows Base UI keyboard, pointer and virtual focus). Drop it inside the list or scroll container. selector can be a priority list. as="span"|"li". Add trailItemClass to items so their own hover bg steps aside. e.g. …items - hint (v1.0.1): Consequence preview on hover/focus: "→ what will happen". content (string content rolls when it changes), detail (pixel meta), kbd, tone=neutral|brand|danger, side, align, delay, closeOnClick, disabled. Fades out quickly on leave. Wrap groups in for instant hand-off. Parts: HintRoot, HintTrigger, HintPopup. e.g. - hover-trail (v1.0.0): One highlight that glides between items as the pointer or keyboard focus moves, and returns to the active item (or fades) when you leave. Items opt in with data-trail. useHoverTrail + trailClass when the root is a Base UI part; moveTrail is the shared glide. e.g. {links.map((l) => )} - icons-hugeicons (v1.0.0): Hugeicons free stroke set (MIT, @hugeicons/core-free-icons) behind every GreedUI icon name. Installs as components/ui/icons.tsx, replacing the pixel set. e.g. npx shadcn@latest add /r/icons-hugeicons.json · every , keeps working - icons-iconoir (v1.0.0): Iconoir regular set (MIT, iconoir-react) behind every GreedUI icon name. Installs as components/ui/icons.tsx, replacing the pixel set. e.g. npx shadcn@latest add /r/icons-iconoir.json · every , keeps working - icons-phosphor (v1.0.0): Phosphor Icons regular weight (MIT, @phosphor-icons/react) behind every GreedUI icon name. Installs as components/ui/icons.tsx, replacing the pixel set. Pass weight via IconContext. e.g. npx shadcn@latest add /r/icons-phosphor.json · every , keeps working - icons-pixelart (v1.0.0): Pixelarticons free set (MIT, pixelarticons) on a 24×24 pixel grid behind every GreedUI icon name. Installs as components/ui/icons.tsx, replacing our pixel set. Sharpest at 24/48px. e.g. npx shadcn@latest add /r/icons-pixelart.json · every , keeps working - icons-remix (v1.0.0): Remix Icon line set (Remix Icon License 1.0: free in apps, no reselling as an icon pack, no logos; @remixicon/react) behind every GreedUI icon name. Installs as components/ui/icons.tsx, replacing the pixel set. e.g. npx shadcn@latest add /r/icons-remix.json · every , keeps working - icons-tabler (v1.0.0): Tabler Icons outline set (MIT, @tabler/icons-react) behind every GreedUI icon name. Installs as components/ui/icons.tsx, replacing the pixel set. e.g. npx shadcn@latest add /r/icons-tabler.json · every , keeps working - icons (v1.0.0): 1-bit pixel icon set on a 16×16 grid (1px outlines, 2px functional glyphs, 12px content box). Crisp at 16/32/48px. Named exports, currentColor, `size` prop. e.g. · · createIcon("Name", glyph(2, ["..##", …])) - inline-confirm (v1.0.0): Two-step destructive action, no modal: first press arms ("Confirm delete?") with a pixel countdown that auto-cancels; Esc/blur cancel; async onConfirm shows spinner then doneLabel. Button props + onConfirm, confirmLabel, doneLabel, timeout (ms), confirmTone. e.g. Delete - input (v1.0.2): Calm text input on Base UI Input: a dotted ring ripples out once on focus, the caret fades instead of blinking. size=sm|md|lg, leading/trailing slots (icon, kbd, button), invalid; picks up Field state (data-invalid) automatically. e.g. } trailing={/} /> - kbd (v1.0.0): Pixel keycap for shortcuts (stepped corners, Geist Pixel). keys=[…] renders a combo; size=sm|md. Dips 1px when its parent button is pressed. e.g. ⌘K · · - menu (v1.0.1): Dropdown of actions. ; MenuItem icon|description|shortcut|tone=neutral|brand|danger, MenuCheckboxItem, MenuRadioGroup+MenuRadioItem, MenuSub label, MenuGroupLabel, MenuSeparator. Compound Menu* parts. e.g. Edit}>DuplicateDelete - menubar (v1.0.0): App-style command bar. of …menu items…; hover-switches instantly between open menus. Compound: MenuRoot + MenubarTrigger + MenuContent. e.g. New… - mesh-gradient (v1.0.5): Smooth, grainy WebGL mesh gradient that blends spots in oklab with a soft noise warp. points=[{x,y,color}]|preset name, warp, grain, speed (0 = still), dither="none"|"bayer"|"halftone", cell. Pauses offscreen, CSS fallback when effects are off. e.g. · - message (v1.0.1): Chat rows without bubbles. . Words fade in with a soft caret, height glides, actions show on hover. sticks to the bottom and offers "jump to latest". Parts: MessageText, MessageTool, MessageActions, MessageAction, MessageCopy, MessageFeedback. e.g. - meter (v1.0.3): A measured value in a known range (quota, battery, strength). variant=bar|pixel (tall, narrow whole-pixel cells with a fixed 2px gap; count fills the width, or set cells for an exact count that stretches to fill it), tone=neutral|brand|positive|warning|danger|info, thresholds={{warning,danger}} picks tone (descending = low is bad), size=sm|md|lg, label, showValue. e.g. · - morph (v1.0.2): Wrapper that glides its own width/height when its content changes size (Dynamic Island style). axis="height"|"width"|"both". swapKey: when it changes, the new content fades and sharpens in while the box resizes. One CSS transition, no per-frame JS. e.g. {step === 1 ? : } - navigation-menu (v1.0.0): Site nav with rich dropdown panels; one shared popup morphs size/position between triggers. NavigationMenu > NavigationMenuList > NavigationMenuItem > NavigationMenuTrigger + NavigationMenuContent > NavigationMenuLink title|description|icon|variant=card|bar. e.g. Docs - number-field (v1.0.9): Numeric input with −/+ steppers and a drag-to-scrub label (Base UI NumberField); stepped values roll in. . Parts: NumberFieldRoot, NumberFieldScrubArea, NumberFieldScrubAreaCursor, NumberFieldGroup, NumberFieldInput, NumberFieldIncrement, NumberFieldDecrement. e.g. - otp-field (v1.0.0): One-time-code entry (Base UI OTPField): pixel cells with stepped corners and a blinking block caret; a ripple runs across when complete. . Parts: OTPFieldRoot, OTPFieldInput, OTPFieldSeparator. e.g. - popover (v1.0.1): Anchored floating panel that grows out of its trigger. or parts PopoverRoot/Trigger/Portal/Positioner/Popup/Arrow(pixel-stepped)/Title/Description/Close. e.g. Share} title="Share link" arrow>… - preview-card (v1.0.1): Hover preview for a link. link text or parts PreviewCardRoot/Trigger/Portal/Positioner/Popup/Arrow. e.g. motion guide - progress (v1.0.3): Task progress. variant=bar|pixel (tall, narrow whole-pixel cells with a fixed 2px gap fill one by one; count fills the width, or set cells for an exact count that stretches to fill it), tone=neutral|brand|positive|warning|danger|info, size=sm|md|lg, value={null} = indeterminate, label, showValue. e.g. · - prompt-input (v1.0.0): Conversational input for AI/agent features: textarea that glides as it grows, ⌘↵ submit (enterSubmits for ↵), send morphs to stop while loading, suggestion chips fill the input, attachments + toolbar slots. value/defaultValue/onValueChange, onSubmit, onStop, status, maxRows, ref (textarea). e.g. - radio-group (v1.0.9): Single choice (Base UI RadioGroup + Radio), same raised fill as Checkbox. ; choice = hairline-divided rows with a sliding accent bar, no boxes. Parts: RadioGroupRoot, RadioRoot, RadioIndicator, Radio. e.g. - reactions (v1.0.0): Reaction chips + picker (ReactionPicker alone works as a toolbar button, icon prop). value/defaultValue/onValueChange of { id, count, mine }[], options of { id, label, icon } (pixel icons by default, emoji strings work too), size=sm|md, picker. Counts roll, yours is highlighted, toggling thumps, the picker pops with a halftone dissolve. e.g. - scribble-check (v1.0.9): Consent checkbox (Base UI Checkbox) that signs itself: a hand-drawn check scribbles in, the label gets a hand underline, and a little pen rides along it with a "signed" note. label, description, underline, pen, note, size=sm|md|lg, tone=brand|neutral|positive, invalid. Parts: ScribbleCheckRoot. e.g. - scroll-area (v1.0.0): Native scrolling with thin overlay scrollbars that appear on hover/scroll and edge fade masks driven by overflow distance. orientation=vertical|horizontal|both, fade. e.g. … · … - select (v1.0.0): Pick from a list; popup aligns the chosen item over the trigger. - sentence (v1.0.0): Settings written as plain sentences with clickable inline tokens. wraps text + . e.g. When a new order comes in, pick an action. - separator (v1.0.0): 1px hairline divider. orientation=horizontal|vertical, variant=line|pixel (stepped dashes), optional label in pixel font. e.g. · · - skeleton (v1.0.0): Shimmering placeholder that matches the shape of what's loading. shape=text|circle|rect, lines (text). Size via className. Reduced motion = static. e.g. · · - slider (v1.0.0): Single or range slider (Base UI Slider): thick inset track with a dotted ruler, pill thumb, fill that glides and tucks into the thumb at 0, rolling value. size, tone=brand|neutral, labels=["Low","High"] (snaps + highlights), dots (ruler, on by default), format, showValue, bubble, thumbLabels, variant=track|slim. Parts: SliderRoot, SliderLabel, SliderValue, SliderControl, SliderTrack, SliderIndicator, SliderRuler, SliderThumb. e.g. · - sparkline (v1.0.1): Tiny SVG trend. data, variant=line|area|dither|bar (dither = Bayer dot fill), texture=dither|grain|none (area: fine ordered-dither dots over the soft fill by default), tone=brand|neutral|positive|warning|danger|info|gradient, labels, format (Intl), interactive crosshair, strokeWidth, min, max. Draws in on mount (CSS), fills its box. e.g. · - spinner (v1.0.0): Pixel "busy" indicator: 8 cells chase around a 3×3 grid. Pure CSS, inherits currentColor. e.g. · - stat (v1.0.2): Inline metric, not a card. label, value (number → AnimatedNumber, digits roll), format, delta (%), deltaLabel, invert (down is good), data (sparkline, sparkline={{ variant: "dither" }} for a dot fill), size=sm|md|lg, pixel, spring (count instead of roll). String label, delta and deltaLabel roll too. = hairline-divided equal columns (4 → 2x2 → 1), sparkline drops under the value when a column is narrow. e.g. - status (v1.0.9): Tiny 3x3 dot-matrix status + label: operational beats like a heart, degraded stutters, down blinks hollow, pending chases, idle flatlines. state, size=sm|md|lg, pulse (animate, off under reduced motion), square (crisp pixels instead of dots), pixel label, uptime=[0..1 | state] (strip of cells on hover/focus), children overrides label. e.g. · Partial outage - switch (v1.0.0): On/off toggle (Base UI Switch): the thumb glides and squashes the same both ways, a dotted glow breathes on hover and a dotted ring pops on toggle. size=sm|md|lg (36/44/52px), tone=brand|neutral, label + description render a settings row. Parts: SwitchRoot, SwitchThumb. e.g. - table (v1.0.0): Hairline data table, no box: sticky header, tabular nums, row hover, selection via TableRow selected (data-selected, brand edge). Table size=sm|md|lg, sticky, maxHeight; TableHead sort="asc"|"desc"|false + onSort, align; TableCell align; TableHeader/Body/Footer/Caption. e.g. NameMRRAcme$4,200
- tabs (v1.0.0): Tabs with a gliding indicator and a hover highlight that glides between tabs. variant=underline|pill, size=sm|md|lg; panels glide to their height and fade in. or compound TabsRoot/List/Tab/Panel. e.g. - textarea (v1.0.2): Multiline input that grows with its content (CSS field-sizing, JS fallback). size=sm|md|lg, invalid, count shows a pixel character counter (pairs with maxLength). Field-aware. e.g.