Files
ADS3-Design-System/DESIGN.md
Richie f4fd1fc04b Rebrand to ADS 3.0 Design System and add DESIGN.md component reference
Convert from Research Synthesiser-specific project to general-purpose ADS 3.0
design system intended to be forked for downstream applications. Add DESIGN.md
following Google Labs spec as machine-readable reference for AI coding agents.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-22 09:59:02 +10:00

20 KiB
Raw Blame History

name, description, version, colors, typography, rounded, spacing, components
name description version colors typography rounded spacing components
ADS 3.0 Design System React component library implementing the NSW Department of Education's ADS 3.0 design language alpha
blue-01 blue-02 blue-03 blue-04 blue-05 red-01 red-02 red-03 red-04 red-05 orange-01 orange-02 orange-03 orange-04 green-01 green-02 green-03 green-04 grey-01 grey-02 grey-03 grey-04 off-white white primary primary-dark error success warning text text-secondary border bg surface
#002664 #146CFD #69B3E7 #CBEDFD #EBF5FF #3E0014 #D7153A #F5C5D0 #FDDDE5 #FFF5F8 #7A3300 #EC6608 #F5B98A #FEF0E4 #005C35 #00A651 #89E5B3 #E0F8EA #22272B #6D7278 #C0C0C0 #E0E0E0 #F4F4F4 #FFFFFF {colors.blue-02} {colors.blue-01} {colors.red-02} {colors.green-02} {colors.orange-02} {colors.grey-01} {colors.grey-02} {colors.grey-04} {colors.off-white} {colors.white}
h1 h2 h3 h4 h5 h6 intro body small caption
fontFamily fontSize fontWeight lineHeight
Public Sans Variable 48px 700 1.25
fontFamily fontSize fontWeight lineHeight
Public Sans Variable 32px 700 1.25
fontFamily fontSize fontWeight lineHeight
Public Sans Variable 24px 600 1.333
fontFamily fontSize fontWeight lineHeight
Public Sans Variable 20px 600 1.4
fontFamily fontSize fontWeight lineHeight
Public Sans Variable 16px 600 1.5
fontFamily fontSize fontWeight lineHeight
Public Sans Variable 14px 600 1.43
fontFamily fontSize fontWeight lineHeight
Public Sans Variable 20px 400 1.4
fontFamily fontSize fontWeight lineHeight
Public Sans Variable 16px 400 1.5
fontFamily fontSize fontWeight lineHeight
Public Sans Variable 14px 400 1.357
fontFamily fontSize fontWeight lineHeight
Public Sans Variable 12px 400 1.5
sm DEFAULT lg xl full
4px 6px 10px 16px 9999px
unit scale
4px Tailwind default (4px base: 1=4px, 2=8px, 3=12px, 4=16px, 6=24px, 8=32px)
button-primary-default button-primary-danger button-secondary-default input card-surface badge-info alert-info
backgroundColor textColor typography rounded height
{colors.primary-dark} {colors.white} {typography.body} {rounded.DEFAULT} 48px
backgroundColor textColor
{colors.red-02} {colors.white}
backgroundColor textColor
transparent {colors.primary-dark}
backgroundColor textColor typography rounded height
{colors.white} {colors.text} {typography.body} {rounded.DEFAULT} 48px
backgroundColor rounded
{colors.surface} {rounded.xl}
backgroundColor textColor rounded
{colors.blue-02} {colors.white} {rounded.full}
backgroundColor rounded
{colors.blue-05} {rounded.DEFAULT}

Overview

ADS 3.0 (Adaptive Design System) is a React component library based on the NSW Department of Education's digital design language. It uses Public Sans as its typeface, a structured blue/grey palette with semantic colour aliases, and consistent border radii and spacing. Components are built with React 19, TypeScript (strict), and Tailwind CSS v4.

All visual values come from design tokens defined in src/tokens/tokens.css. Components never use raw colour values — they reference semantic or domain-specific token classes via Tailwind utilities.

Colors

The palette is organised in three layers:

  1. Palette — Raw values (blue-01 through blue-05, grey-01 through grey-04, etc.). Never use these directly in component code.
  2. Semantic — Purpose-based aliases (primary, error, success, warning, text, surface, bg, border). Use these for general UI.
  3. Domain — Component-specific tokens (button-default, control-border, badge-info, alert-error-bg, tag-navy, etc.). Use these within their respective components.

The primary brand colour is blue-01 (#002664, dark navy) for interactive elements like buttons and links. blue-02 (#146CFD) is the brighter accent used for info states, focus rings, and highlights.

Typography

The system uses Public Sans Variable (loaded via @fontsource-variable/public-sans). The type scale runs from caption (12px) to h1 (48px). Headings use weight 600700; body text uses 400.

Use Tailwind's text-{scale} utilities: text-h1, text-h2, text-h3, text-h4, text-h5, text-h6, text-intro, text-body, text-small, text-caption.

Layout & Spacing

Spacing follows Tailwind's default 4px base scale. Common values:

  • p-2 (8px), p-3 (12px), p-4 (16px), p-6 (24px) for padding
  • gap-2 (8px), gap-3 (12px), gap-4 (16px) for flex/grid gaps
  • space-y-4 (16px) for stacked content

No custom spacing tokens are defined — the Tailwind defaults are sufficient. This may change in future.

Elevation & Depth

Two shadow levels:

  • shadow-default — subtle lift for cards and surfaces (0 1px 3px rgba(0,0,0,0.08))
  • shadow-md — elevated elements like dropdowns and dialogs (0 4px 6px -1px rgba(0,0,0,0.1))

Shapes

Border radii use the custom scale:

  • rounded-sm (4px) — small elements, tags
  • rounded-default (6px) — buttons, inputs, alerts
  • rounded-lg (10px) — cards, dialogs
  • rounded-xl (16px) — large containers
  • rounded-full (9999px) — badges, circular buttons

Components

Button

Interactive button with variant/intent/size matrix.

Prop Type Default Description
variant 'primary' | 'secondary' | 'tertiary' 'primary' Visual weight
intent 'default' | 'danger' | 'subtle' | 'neutral' 'default' Semantic purpose
size 'default' | 'comfortable' | 'compact' 'default' Height and padding
loading boolean false Shows spinner, disables interaction
leftIcon ReactNode Icon before label
rightIcon ReactNode Icon after label
import { Button } from '@/components/atoms/Button'

<Button variant="primary" intent="default">Save</Button>
<Button variant="secondary" intent="danger" leftIcon={<Trash2 />}>Delete</Button>
<Button variant="tertiary" size="compact">Cancel</Button>

IconButton

Icon-only button with required aria-label.

Prop Type Default Description
variant 'primary' | 'secondary' | 'tertiary' 'primary' Visual weight
intent 'default' | 'danger' | 'neutral' 'default' Semantic purpose
size 'default' | 'large' | 'compact' | 'small' | 'xsmall' 'default' Button dimensions
shape 'circle' | 'square' 'circle' Border radius
icon ReactNode Required. The icon element
aria-label string Required. Accessible label
import { IconButton } from '@/components/atoms/IconButton'
import { X, Settings } from 'lucide-react'

<IconButton icon={<X />} aria-label="Close" variant="tertiary" />
<IconButton icon={<Settings />} aria-label="Settings" shape="square" size="compact" />

Input

Text input with label, description, hint, error, and icon slots.

Prop Type Default Description
label string Required. Visible label
description string Help text below label
hint string Inline hint (right-aligned)
error string Error message (replaces description)
variant 'outlined' | 'stacked' 'outlined' Label position
size 'default' | 'compact' 'default' Input height
leftIcon ReactNode Icon inside input (left)
rightIcon ReactNode Icon inside input (right)
import { Input } from '@/components/atoms/Input'
import { Search, Mail } from 'lucide-react'

<Input label="Email" type="email" placeholder="you@example.com" leftIcon={<Mail />} />
<Input label="Search" variant="stacked" size="compact" leftIcon={<Search />} />
<Input label="Name" error="Name is required" />

Textarea

Multi-line text input with optional auto-resize.

Prop Type Default Description
label string Required. Visible label
description string Help text below label
hint string Inline hint
error string Error message
variant 'outlined' | 'stacked' 'outlined' Label position
resize 'vertical' | 'horizontal' | 'both' | 'none' 'vertical' Resize handle
autoResize boolean false Auto-grow with content
import { Textarea } from '@/components/atoms/Textarea'

<Textarea label="Notes" placeholder="Add your notes..." rows={4} />
<Textarea label="Description" autoResize error="Too short" />

Select

Custom dropdown select with keyboard navigation.

Prop Type Default Description
label string Required. Visible label
description string Help text
hint string Inline hint
error string Error message
variant 'outlined' | 'stacked' 'outlined' Label position
placeholder string Placeholder text
options SelectOption[] Required. { value, label, disabled? }
value string Controlled value
defaultValue string Uncontrolled default
onChange (value: string) => void Change handler
disabled boolean false Disables the select
import { Select } from '@/components/atoms/Select'

<Select
  label="Status"
  options={[
    { value: 'active', label: 'Active' },
    { value: 'archived', label: 'Archived' },
    { value: 'draft', label: 'Draft' },
  ]}
  onChange={(val) => console.log(val)}
/>

Checkbox

Checkbox with label, description, error, and indeterminate state.

Prop Type Default Description
label string Visible label
description string Help text below label
error string Error message
indeterminate boolean false Shows minus icon
import { Checkbox } from '@/components/atoms/Checkbox'

<Checkbox label="I agree to the terms" />
<Checkbox label="Select all" indeterminate description="Some items are selected" />
<Checkbox label="Required" error="You must agree" />

Radio / RadioGroup

Radio buttons grouped with shared state.

Prop (RadioGroup) Type Default Description
label string Group label
description string Group help text
error string Error message
value string Controlled value
defaultValue string Uncontrolled default
orientation 'vertical' | 'horizontal' 'vertical' Layout direction
onChange (value: string) => void Change handler
Prop (Radio) Type Default Description
label string Radio label
value string Required. Radio value
import { RadioGroup, Radio } from '@/components/atoms/Radio'

<RadioGroup label="Priority" orientation="horizontal" onChange={setPriority}>
  <Radio value="low" label="Low" />
  <Radio value="medium" label="Medium" />
  <Radio value="high" label="High" />
</RadioGroup>

Switch

Toggle switch with label and description.

Prop Type Default Description
label string Visible label
description string Help text
checked boolean false Controlled state
onChange (checked: boolean) => void Change handler
disabled boolean false Disables the switch
import { Switch } from '@/components/atoms/Switch'

<Switch label="Dark mode" checked={dark} onChange={setDark} />
<Switch label="Notifications" description="Receive email alerts" />

Badge

Small status indicator with colour variants.

Prop Type Default Description
variant 'navy' | 'info' | 'info-light' | 'success' | 'success-light' | 'error' | 'error-light' | 'warning' | 'warning-light' | 'neutral' | 'white' 'navy' Colour variant
leftIcon ReactNode Icon before text
rightIcon ReactNode Icon after text
import { Badge } from '@/components/atoms/Badge'

<Badge variant="success">Active</Badge>
<Badge variant="error-light">Overdue</Badge>
<Badge variant="info" leftIcon={<Clock />}>Pending</Badge>

Chip

Selectable/dismissible filter chip.

Prop Type Default Description
children ReactNode Required. Chip label
selected boolean false Selected state
onDismiss () => void Shows dismiss icon, makes removable
rightIcon ReactNode Custom right icon
import { Chip } from '@/components/atoms/Chip'

<Chip selected={isActive} onClick={toggle}>Qualitative</Chip>
<Chip onDismiss={() => removeFilter('date')}>2024</Chip>

Tag

Coloured label for categorisation.

Prop Type Default Description
variant 'outline' | 'filled' | 'light' 'outline' Visual style
color 'navy' | 'blue' | 'green' | 'red' | 'orange' | 'grey' 'navy' Colour
size 'default' | 'sm' 'default' Tag size
icon ReactNode Leading icon
onRemove () => void Shows remove button
import { Tag } from '@/components/atoms/Tag'

<Tag color="blue" variant="light">Research</Tag>
<Tag color="green" variant="filled" onRemove={handleRemove}>Complete</Tag>

Tooltip

Floating tooltip on hover/focus with arrow.

Prop Type Default Description
content ReactNode Required. Tooltip content
placement Floating UI Placement 'top' Position relative to trigger
delay number | { open?, close? } { open: 400, close: 0 } Show/hide delay (ms)
children ReactElement Required. Trigger element
import { Tooltip } from '@/components/atoms/Tooltip'

<Tooltip content="Save your changes">
  <Button>Save</Button>
</Tooltip>

Card

Container with variant styles. Composed of Card, CardHeader, CardContent, CardFooter.

Prop (Card) Type Default Description
variant 'surface' | 'outlined' | 'elevated' | 'filled' 'surface' Visual style
Prop (CardHeader) Type Default Description
action ReactNode Action element (top-right)
import { Card, CardHeader, CardContent, CardFooter } from '@/components/molecules/Card'

<Card variant="elevated">
  <CardHeader action={<IconButton icon={<MoreHorizontal />} aria-label="Options" variant="tertiary" />}>
    <h3>Card Title</h3>
  </CardHeader>
  <CardContent>
    <p>Card body content goes here.</p>
  </CardContent>
  <CardFooter>
    <Button variant="tertiary" size="compact">View details</Button>
  </CardFooter>
</Card>

Accordion

Collapsible content sections. Composed of Accordion, AccordionItem, AccordionTrigger, AccordionContent.

Prop (Accordion) Type Default Description
type 'single' | 'multiple' 'single' One or many open at once
collapsible boolean false Allow closing all (single mode)
defaultValue string | string[] Initially open item(s)
value string | string[] Controlled open state
onValueChange (value) => void Change handler
Prop (AccordionItem) Type Default Description
value string Required. Unique item identifier
disabled boolean false Prevents opening
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/components/molecules/Accordion'

<Accordion type="single" collapsible>
  <AccordionItem value="section-1">
    <AccordionTrigger>What is this?</AccordionTrigger>
    <AccordionContent>Explanation here.</AccordionContent>
  </AccordionItem>
  <AccordionItem value="section-2">
    <AccordionTrigger>How does it work?</AccordionTrigger>
    <AccordionContent>Details here.</AccordionContent>
  </AccordionItem>
</Accordion>

Alert

Contextual message with icon, title, close button, and action slot.

Prop Type Default Description
variant 'info' | 'warning' | 'error' | 'success' | 'neutral' 'info' Alert type and colour
title string Bold heading
onClose () => void Shows close button
action ReactNode Action element (e.g. link or button)
icon ReactNode Custom icon (default based on variant)
import { Alert } from '@/components/molecules/Alert'

<Alert variant="success" title="Saved" onClose={dismiss}>
  Your changes have been saved successfully.
</Alert>
<Alert variant="error" title="Error">Something went wrong.</Alert>

Dialog

Modal dialog with backdrop, size options, and composed sections.

Prop (Dialog) Type Default Description
open boolean Required. Controls visibility
onClose () => void Required. Close handler
size 'sm' | 'default' | 'lg' | 'full' 'default' Max width
closeOnBackdrop boolean true Close when clicking backdrop

Composed with DialogHeader, DialogContent, DialogFooter.

import { Dialog, DialogHeader, DialogContent, DialogFooter } from '@/components/molecules/Dialog'

<Dialog open={isOpen} onClose={() => setIsOpen(false)} size="sm">
  <DialogHeader onClose={() => setIsOpen(false)}>Confirm</DialogHeader>
  <DialogContent>Are you sure you want to delete this?</DialogContent>
  <DialogFooter>
    <Button variant="tertiary" onClick={() => setIsOpen(false)}>Cancel</Button>
    <Button intent="danger">Delete</Button>
  </DialogFooter>
</Dialog>

Popover

Floating content panel triggered by a child element. Composed of Popover, PopoverTrigger, PopoverContent.

Prop (Popover) Type Default Description
placement Floating UI Placement 'bottom' Position relative to trigger
open boolean Controlled open state
onOpenChange (open: boolean) => void Open state handler
import { Popover, PopoverTrigger, PopoverContent } from '@/components/molecules/Popover'

<Popover placement="bottom-start">
  <PopoverTrigger>
    <Button variant="secondary">Options</Button>
  </PopoverTrigger>
  <PopoverContent>
    <p>Popover content here.</p>
  </PopoverContent>
</Popover>

Do's and Don'ts

Do:

  • Use semantic token classes (bg-primary, text-error, border-control-border), never palette tokens (bg-blue-01)
  • Use the cn() utility from @/lib/utils for conditional classes
  • Use forwardRef for components wrapping native elements
  • Use semantic HTML (<button>, <input>, <dialog>) — not <div onClick>
  • Include aria-label on icon-only buttons
  • Use lucide-react for icons (already a dev dependency)

Don't:

  • Hardcode colour hex values in component code
  • Use inline styles (except for truly dynamic values like calculated positions)
  • Use CSS modules or styled-components
  • Skip the label prop on form controls — all inputs must have visible labels
  • Nest interactive elements (button inside button, link inside button)