Buttons
Neo defines four distinct button types with strict usage hierarchy. Only one Primary Button is allowed per view.Primary Button
The highest-emphasis call to action on any screen.- Default
- Focused
- Disabled
Navy background, white text, full opacity. Cursor pointer.
Limit to one Primary Button per view. It represents the single most important action the user can take.
Never place two Primary Buttons side by side. Demote the secondary action to a Secondary Button.
Secondary Button
Used for supporting actions alongside a Primary Button or as standalone mid-emphasis CTAs.- Default
- Focused
- Disabled
Outlined or subtle fill background. Navy text. Cursor pointer.
Optimise Prompts Button
A ghost-style button exclusively for prompt editing contexts. Always paired with a sparkle icon.This button only appears within prompt configuration views. Do not reuse the sparkle + ghost pattern for other features.
Disconnect Button
A destructive ghost button used for disconnecting integrations or removing linked accounts. Requires a confirmation step.Temperature Slider
A horizontal slider for controlling AI creativity/precision from 0.0 to 1.0.Text Inputs
Standard Text Input
- Default
- Filled
- Focused
- Error
Empty input with placeholder text in
--color-disabled. Border is --color-border.Text Input with Image Upload
Same specs as Standard Text Input, with an image upload icon button (20x20) positioned inside the input on the right edge. Clicking it opens a file picker filtered to image types.Dropdowns
Standard Dropdown
Standard Dropdown
A select menu with a list of text options. Same dimensions as Text Input (536x40, 5px radius). Chevron-down icon on the right. Options panel appears below with 8px gap and
--radius-md corners.Dropdown with Body Copy
Dropdown with Body Copy
Each option includes a label (—type-label-lg) and a description line (—type-caption) below it. Option rows are taller to accommodate the two lines. Use when options need explanation.
Dropdown with Header + Body Copy
Dropdown with Header + Body Copy
Options are grouped under section headers (—type-label-sm, uppercase). Each option has label + body copy. Use for categorized option lists like model selection.
Use the simplest dropdown variant that communicates the options clearly. Only add body copy or headers when options genuinely need explanation.
Never render more than 8-10 options in a visible dropdown list. Use search/autocomplete for longer lists (e.g., model selection with 300+ items).
Toggle
A binary on/off switch for settings and feature flags.Toggles take effect immediately — no save button required. If the change needs confirmation, use a checkbox + save button pattern instead.
Upload Cards
Document Upload Card
Dimensions: 347 x 86px. Displays file name, size, and a progress/status indicator.- Uploaded (Success)
- In Progress
- Failed
File icon + name + size on the left. Green check-circle icon on the right. Background:
--color-white. Border: --color-border.Instagram Upload Card
Same 3 states and dimensions as Document Upload Card, but with an image thumbnail (48x48,--radius-sm) on the left instead of a file icon.
Always show a retry action on failed uploads. Never leave the user in a dead-end state.
Do not auto-dismiss failed upload cards. The user must acknowledge the failure or retry.
Usage Credit Card
Displays organization credit consumption. Dimensions: 359 x 199px.- 0% Used
- Normal Usage
- High Usage (>80%)
- Overage
Empty progress bar. Gray fill. Usage text shows “X used.” Clean starting state.
Data Tables
All tables use--type-body for cell content, --type-label-sm for column headers, and --color-border for row dividers.
Agent Table (4 columns)
User Table (6 columns)
Knowledge Base Table (7 columns)
Paginate tables beyond 20 rows. Provide search and column sorting on all tables.
Never render an unbounded table. Always set a page size and show a “Load More” or pagination control.
TabNav (Sub-Navigation)
The single primitive for in-page tab bars —apps/web/src/components/ui/TabNav.tsx. Segmented-control presentation with two selectable modes (route vs. state); see docs/developer/design-system/navigation.mdx for the full mode/props reference.
- Active item
- Idle item
- Disabled item
White background,
--neo-border border, --shadow-sm, 600-weight text in
--text-primary.Use
<TabNav> for every in-page tab bar. Never hand-roll a border-b-2 underline row or a row of <Button variant="default|outline"> as a view switcher.Do not build a bespoke mobile tab dropdown. Pass
mobileMode="dropdown"
(recommended at 5+ items or long labels) and TabNav renders its own.PageHeader
The single primitive for every dashboard page title —apps/web/src/components/ui/PageHeader.tsx. Props: title, description?, actions?, tabs?, className?.
Anatomy is fixed and owned by the component: Breadcrumb (rendered globally by
(dashboard)/layout.tsx, not by PageHeader) → Title/Description/Actions → Tabs → page content. Always plain on the page background — never wrapped in a card or a full-bleed bar.
Use
<PageHeader> for every page title. Section layouts (admin, operator, settings, knowledge/[id]/graph) render the single PageHeader for their section; child pages use <h2> and must not render a second one.Never hand-roll an
<h1> — 14 different title classNames existed across
the app before this component.Admin Config List
The pattern for admin/config surfaces presenting many settings grouped into categories — shared byadmin/system-config and admin/prompt-config. Reference: apps/web/src/app/(dashboard)/admin/system-config/page.tsx (~L418–610).
Editing happens inline within the row, driven by an
editingKey-style state — not a nested second disclosure. Status uses <Badge> variants, never hand-rolled colored chips.
Use the admin config list for admin/config surfaces with many settings that group naturally into categories.
Don’t invent a fourth collapsible-list shape. Use a plain
Card +
divide-y for a simple flat list, or the table conventions above for
tabular data.Badge
The status-chip primitive —apps/web/src/components/ui/Badge.tsx. variant prop selects a token-backed color pairing; default is neutral.
default, secondary, destructive, and outline also exist as legacy aliases for backward compatibility — prefer the semantic names above in new code.
Use
<Badge variant> for every status chip. Status colors used elsewhere on the page must resolve through the same variant mapping, not a hand-rolled bg-*/text-* pair.
