Aici Design System — V.1.0

Consistent, accessible
interfaces built at scale.

A customised shadcn/ui component library built on Radix UI. Neutral palette, Inter typography, zero extra dependencies.

01

Installation

01

Create project

TypeScript, Tailwind CSS, App Router, no src/ directory, @/* import alias.

pnpm create next-app@latest my-app \ --typescript --tailwind --app --src-dir no --import-alias "@/*" cd my-app
02

Add Aici Design System

Run from inside your project directory. Copies all components, injects design tokens into globals.css, and installs dependencies — automatically.

npx @aici/ui .
components/ui/*All UI components
lib/utils.tscn() utility
hooks/*Shared hooks
public/Logo SVG assets
globals.cssDesign tokens injected
package.jsonDependencies installed

Same command to update — re-runs cleanly without touching your own code.

03

Add Inter font in layout.tsx

Load Inter via next/font/google and attach it as --font-inter CSS variable on <html>.

import { Inter } from "next/font/google"; import "./globals.css"; const inter = Inter({ subsets: ["latin"], variable: "--font-inter", display: "swap", }); export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en" className={inter.variable}> <body>{children}</body> </html> ); }
04

Import components and build

All components live in @/components/ui/. Dark mode is toggled by adding or removing the "dark" class on <html>.

import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardContent } from "@/components/ui/card";
02

Logo & Icons

App Icon (preset: app="appName")

Education
Research
Practice
Site
Urbanism
Concept
Technical
Construction
InUse
Bordered

Props: app | iconColor, backgroundColor, borderColor, size

Usage

import AppIcon, { APP_PRESETS, type AppPresetName } from "@/components/ui/app-icon"; // With preset (iconColor, backgroundColor applied automatically, no border) <AppIcon app="Education" size={40} /> <AppIcon app="Site" size={32} /> <AppIcon app="InUse" size={56} /> // Manual colors, no border <AppIcon iconColor="#262626" backgroundColor="#9C9C9C" size={40} /> // Manual colors + border (1px when borderColor is set) <AppIcon iconColor="#262626" backgroundColor="#E8E8E8" borderColor="#D5B56E" size={40} /> // Bordered only (transparent bg, icon + border #D5B56E) <AppIcon iconColor="#D5B56E" backgroundColor="transparent" borderColor="#D5B56E" size={40} />

A Icon (standalone)

Default 18×22px at size 40; scales proportionally. Props: size, color.

size 40
size 60
#D5B56E
import AIcon from "@/components/ui/a-icon"; // Default (size 40 = 18×22px) <AIcon /> // Custom size (scales proportionally: 60 → 27×33) <AIcon size={40} /> <AIcon size={60} /> // Custom color <AIcon color="#262626" /> <AIcon color="#D5B56E" size={60} />

AICI Logo

Default 90×15px; scales proportionally. Props: size, color.

size 90
size 120
#D5B56E
import AiciLogo from "@/components/ui/aici-logo"; // Default 90×15 <AiciLogo /> // Custom size (scales proportionally) <AiciLogo size={90} /> <AiciLogo size={120} /> // Custom color <AiciLogo color="#262626" /> <AiciLogo size={60} color="#D5B56E" />

Chevron Icons

Props: size, stroke (default #262626), strokeWidth (default 0.5).

left
right
up
down

Sizes · #262626

16
18
20
14, #D5B56E
import { Chevron } from "@/components/ui/chevron-icons"; // or individual: LeftChevron, RightChevron, UpChevron, DownChevron // Unified component — variant: "left" | "right" | "up" | "down" <Chevron variant="left" /> <Chevron variant="right" /> <Chevron variant="up" /> <Chevron variant="down" /> // Props: variant, size, stroke (default #262626), strokeWidth (default 0.5) <Chevron variant="left" size={16} stroke="#262626" /> <Chevron variant="down" size={20} stroke="#9c9c9c" strokeWidth={1} />
03

Colors

Foreground

Muted

Background

White

04

Typography

Heading

15 / 700

The quick brown fox

Subheading

15 / 500

The quick brown fox

Body

14 / 300

Consistent, accessible interfaces built at scale.

Label

12 / 500

Input label · Button · Nav

Caption

10 / 300

Helper text and supporting descriptions

Label Uppercase

12 / 500 + 0.15em

Input label · Button · Nav

Caption Uppercase

10 / 300 + 0.15em

Helper text and descriptions

Micro Uppercase

8 / 300 + 0.15em

Design system · Nav
05

Theme

Copy tailwind.config.js to your project root.

/* globals.css — Tailwind v4 (CSS-only config) */ @import "tailwindcss"; @import "tw-animate-css"; @custom-variant dark (&:is(.dark *)); @layer base { :root { /* Brand palette (static) */ --brand: #262626; --brand-light: #f7f7f7; --brand-muted: #9c9c9c; --gold: #d5b56e; /* Semantic — light */ --background: #f7f7f7; --foreground: #262626; --primary: #262626; --primary-foreground: #f7f7f7; --secondary: #f7f7f7; --secondary-foreground: #262626; --muted: oklch(0.94 0 0); --muted-foreground: #9c9c9c; --accent: #9c9c9c; --accent-foreground: #262626; --destructive: oklch(0.58 0.22 27); --border: oklch(0.88 0 0); --input: oklch(0.88 0 0); --ring: #9c9c9c; --radius: 0.5rem; } html.dark { /* Semantic — dark */ --background: #262626; --foreground: #f7f7f7; --primary: #f7f7f7; --primary-foreground: #262626; --secondary: #262626; --secondary-foreground: #f7f7f7; --muted: oklch(0.24 0 0); --border: oklch(1 0 0 / 12%); --input: oklch(1 0 0 / 12%); } } @theme inline { --font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif; --font-mono: var(--font-inter), ui-sans-serif, system-ui, sans-serif; /* Font sizes */ --text-2xs: 0.5rem; --text-3xs: 0.5625rem; --text-4xs: 0.625rem; --text-11: 0.6875rem; /* Tracking */ --tracking-tight: -0.02em; --tracking-ui: 0.06em; --tracking-label: 0.08em; --tracking-nav: 0.12em; --tracking-caps: 0.15em; --tracking-hero: 0.22em; /* Spacing */ --spacing-1\.25: 0.3125rem; /* 5px */ /* Radius */ --radius-none: 0; --radius-sm: 0.3125rem; --radius: 0.5rem; --radius-full: 9999px; /* Brand */ --color-brand: var(--brand); --color-brand-light: var(--brand-light); --color-brand-muted: var(--brand-muted); --color-gold: var(--gold); /* Semantic */ --color-background: var(--background); --color-foreground: var(--foreground); --color-primary: var(--primary); --color-primary-foreground: var(--primary-foreground); --color-secondary: var(--secondary); --color-secondary-foreground: var(--secondary-foreground); --color-muted: var(--muted); --color-muted-foreground: var(--muted-foreground); --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); --color-destructive: var(--destructive); --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); } @layer utilities { .interactive { opacity: 0.8; transition: opacity 150ms ease; } .interactive:hover, .interactive:active { opacity: 1; } .interactive:disabled { opacity: 0.4; cursor: not-allowed; } }
06

Buttons & Badges

DefaultSecondaryOutlineError
import { Button } from "@/components/ui/button"; <Button>Default</Button> <Button variant="secondary">Secondary</Button> <Button variant="outline">Outline</Button> <Button variant="ghost">Ghost</Button> <Button variant="destructive">Destructive</Button> <Button size="sm">Small</Button> <Button disabled>Disabled</Button>
import { Badge } from "@/components/ui/badge"; <Badge>Default</Badge> <Badge variant="secondary">Secondary</Badge> <Badge variant="outline">Outline</Badge> <Badge variant="destructive">Error</Badge>

Text Button

import TextButton from "@/components/ui/text-button"; <TextButton text="Label" onClick={() => {}} /> <TextButton text="Selected" onClick={() => {}} selected /> <TextButton text="Disabled" onClick={() => {}} disabled /> <TextButton text="Muted" onClick={() => {}} color="var(--muted-foreground)" /> <TextButton text="Destructive" onClick={() => {}} color="var(--destructive)" />

Rounded Button

import RoundedButton from "@/components/ui/rounded-button"; <RoundedButton text="Save" onClick={() => {}} /> <RoundedButton text="Cancel" onClick={() => {}} color="var(--muted-foreground)" width={80} /> <RoundedButton text="Delete" onClick={() => {}} color="var(--destructive)" />
07

Inputs

import { Input } from "@/components/ui/input"; <Input placeholder="Full name" /> <Input type="email" placeholder="Email" /> <Input variant="borderless" placeholder="No border" /> <Input disabled placeholder="Disabled" />
import { Textarea } from "@/components/ui/textarea"; <Textarea placeholder="Write your message..." rows={3} /> <Textarea variant="borderless" placeholder="No border" rows={2} />
import Select from "@/components/ui/select"; const [value, setValue] = useState("design"); <Select options={["design", "engineering", "product"]} value={value} onChange={setValue} />
import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; const [checked, setChecked] = useState(false); <div className="flex items-center gap-2.5"> <Checkbox checked={checked} onCheckedChange={(v) => setChecked(v === true)} /> <Label>Subscribe to newsletter</Label> </div>
import RoundedInput from "@/components/ui/rounded-input"; <RoundedInput placeholder="Email or username" /> <RoundedInput type="pwd" placeholder="Password" showLabel="Show" hideLabel="Hide" />
08

Components

Separator

Horizontal

0.5px

1px

2px

Vertical

Section0.5px
Section1px
Section2px
import { Separator } from "@/components/ui/separator"; // Horizontal (default) — 1px <Separator /> // Horizontal — 0.5px <Separator thickness="0.5" /> // Horizontal — 2px <Separator thickness="2" /> // Vertical — 1px (needs a flex container with defined height) <div className="flex h-8 items-center gap-4"> <span>Left</span> <Separator orientation="vertical" /> <span>Right</span> </div> // Vertical — 0.5px / 2px <Separator orientation="vertical" thickness="0.5" /> <Separator orientation="vertical" thickness="2" />
Card · outlined

bg-secondary · border-primary · text-primary

variant="outlined"

import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; // Default <Card> <CardHeader><CardTitle>Title</CardTitle></CardHeader> <CardContent>Content</CardContent> </Card> // Outlined (bg-secondary · border-primary · text-primary) <Card variant="outlined"> <CardContent>Content</CardContent> </Card>
Date Pickers

Default (placeholder text)

segmented (always three fields)

import DatePickerSegmented from "@/components/ui/date-picker-segmented"; const [date, setDate] = useState<string | undefined>(); // Default: single placeholder text "DD.Month.YYYY" <DatePickerSegmented value={date} onChange={setValue} /> // segmented: always shows three clickable fields <DatePickerSegmented value={date} onChange={setValue} segmented /> // Custom placeholder <DatePickerSegmented value={date} onChange={setValue} placeholder="Pick a date" />
Scroll Area
Row 1
Row 2
Row 3
Row 4
Row 5
Row 6
Row 7
Row 8
Row 9
Row 10
Row 11
Row 12
Row 13
Row 14
Row 15
Row 16
Row 17
Row 18
Row 19
Row 20
import ScrollAreaComponent from "@/components/ui/scroll-area-component"; <ScrollAreaComponent className="h-[120px]" thumbHeight={24} thumbWidth={2}> {items.map((item, i) => ( <div key={i} className="py-1.5 text-xs border-b border-border/50 px-1"> {item} </div> ))} </ScrollAreaComponent>
Resizable

Drag to resize; double‑click handle to expand to 100%.

import ResizableComponent from "@/components/ui/resizable-component"; // Drag handle to resize; double-click to expand to 100% <ResizableComponent minHeight={20} maxHeight={60} persistId="my-resizable" > <p>Content area (layout persisted via localStorage)</p> </ResizableComponent>
Circle

12px

import Circle from "@/components/ui/circle"; // variant: "empty" | "filled" <Circle variant="empty" size={18} /> <Circle variant="filled" size={18} /> <Circle variant="empty" size={12} />
Popover
import { PopoverRoot, PopoverTrigger, PopoverContent } from "@/components/ui/popover"; <PopoverRoot> <PopoverTrigger>Open popover</PopoverTrigger> <PopoverContent side="bottom" align="start" contentWidth={220} contentHeight={100} className="p-4" > Popover content </PopoverContent> </PopoverRoot>
Dropdown Menu
import { DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuItem, DropdownMenuSeparator, } from "@/components/ui/dropdown-menu"; <DropdownMenuRoot> <DropdownMenuTrigger>Open menu</DropdownMenuTrigger> <DropdownMenuContent align="start"> <DropdownMenuGroup> <DropdownMenuLabel>Account</DropdownMenuLabel> <DropdownMenuItem>Profile</DropdownMenuItem> <DropdownMenuItem>Settings</DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuItem>Log out</DropdownMenuItem> </DropdownMenuContent> </DropdownMenuRoot>
Context Menu
Right-click here
import { ContextMenuRoot, ContextMenuTrigger, ContextMenuContent, ContextMenuGroup, ContextMenuLabel, ContextMenuItem, ContextMenuSeparator, } from "@/components/ui/context-menu"; <ContextMenuRoot> <ContextMenuTrigger>Right-click here</ContextMenuTrigger> <ContextMenuContent> <ContextMenuGroup> <ContextMenuLabel>Actions</ContextMenuLabel> <ContextMenuItem>Copy</ContextMenuItem> <ContextMenuItem>Paste</ContextMenuItem> </ContextMenuGroup> <ContextMenuSeparator /> <ContextMenuItem>Cut</ContextMenuItem> </ContextMenuContent> </ContextMenuRoot>
Carousel

Two per row

Single

import CarouselComponent from "@/components/ui/carousel-component"; // Two per row (default) <CarouselComponent items={["Option A", "Option B", "Option C", "Option D"]} value={value} onChange={setValue} /> // Single column <CarouselComponent items={["Option A", "Option B"]} value={value} onChange={setValue} single />
Collapsible
import CollapsibleComponent from "@/components/ui/collapsible-component"; import TextButton from "@/components/ui/text-button"; const [open, setOpen] = useState(false); <CollapsibleComponent open={open} onOpenChange={setOpen} trigger={<TextButton text={open ? "Collapse" : "Expand"} onClick={() => {}} />} triggerPosition="bottom" buttonPosition="left" > <p>Collapsible content</p> </CollapsibleComponent>
Accordion

iconPosition="left" iconVariant="right" → > Title

import AccordionComponent from "@/components/ui/accordion-component"; // Icon left → pointing right when closed, up when open <AccordionComponent title="Section title" iconPosition="left" iconVariant="right" > Content here </AccordionComponent> // Icon right → pointing left when closed, up when open <AccordionComponent title="Right title" titlePosition="right" iconPosition="right" iconVariant="left" > Content here </AccordionComponent> // Props: title, value, defaultOpen, titlePosition, iconPosition, // iconVariant, openIconVariant, className