Overview

The single source of truth for how ITC products look and behave.

Why this exists

ITC ships fast with AI-assisted development. Speed without a shared visual language produced UI that is inconsistent across apps, cluttered and visibly "AI generic". This system makes the default output good: humans and AI tools build from the same tokens, specs and rules, so consistency is the path of least resistance.

Ambition: ITC products feel calm, warm and considered: well-spaced, plainly worded and recognisably one family across every ITC product, web and mobile. Clearly better than CBORD/Illumia, TouchNet and legacy campus-card software, and friendlier than typical admin tools.

Principles

  • 1

    Quiet by default, loud on purpose.

    Neutral surfaces; colour reserved for action, selection and status.

  • 2

    Comfortable by default, compact for data.

    Generous space around content (32px gutters, 24px cards, 48px between sections); tables and toolbars use the compact sizes.

  • 3

    Hierarchy from size and space, not boxes.

    Big clear titles and white space do the grouping. Box only what needs it; side rails are unboxed lists.

  • 4

    Same anatomy every time.

    One way to do a thing: one button per role, one table, one settings row, one modal.

  • 5

    Say what matters.

    Pages open with a plain sentence that summarises and links to what needs attention; every setting says what it does.

  • 6

    Accessible is non-negotiable.

    WCAG 2.2 AA (supports ITC's VPAT/HECVAT obligations).

  • 7

    Same family, different rooms.

    All apps share tokens; each tunes density to its users.

Start here

Engineersdocs/consuming.md
Claude Code / AI toolsAI-RULES.md
The whyDESIGN.md
Logosdocs/brand.md
UI copydocs/tonality.md
MatTODO.md

Why a Design System?

One shared set of rules and ready-made parts that every ITC product is built from. Its biggest job is not colour or fonts: it makes every common task work the same way everywhere, makes our products accessible by default, and gives them the finish of a purpose-built platform.

What a design system is

It's the agreed way we build screens. Decisions like "where does the main button go?", "how do we confirm a save?" or "how do we warn someone before they delete something?" are made once, written down, and turned into ready-made parts that every developer and AI tool uses. Nobody re-decides them screen by screen, so users get the same experience wherever they are in our products.

The biggest change: the same job works the same way, everywhere

Today, AI-assisted builds of AVRO solve these moments differently from screen to screen. The design system sets one pattern for each, so users learn it once.

When someone…It always works like this
Looks for the main actionOne primary button per page, always top-right in the page header, named for what it does ("Add client", not "Submit"). In dialogs it sits bottom-right, with Cancel to its left.
Completes an actionA short confirmation toast appears bottom-right saying exactly what happened ("Jordan Lee added"), with Undo where possible. No pop-up boxes to dismiss.
Needs to know about a problemAn alert at the top of the section says what's wrong and how to fix it, with a link to do so ("12 photos are waiting for approval. Review photos").
Makes a mistake in a formThe message appears under the field, says what to enter and gives an example. Labels are always visible, never placeholder text that disappears.
Deletes or archives somethingA confirmation dialog names what and how many ("Archive 2 clients?"), explains the consequence, and the button repeats it. The safe choice is the default.
Acts on a row in a listOne "more" menu per row, with destructive actions last, instead of loose delete icons on every row.
Searches or filters a listThe same filter bar on every list: search, filters, applied filters shown as chips with "Clear all", then the same table, pagination and bulk-selection bar.
Finds nothing thereAn empty state says what belongs here and how to start, and tells "nothing yet" apart from "no results" (with Clear filters).
Changes a settingEvery setting says what it does in plain words. Switches take effect immediately and confirm with a toast; no hunting for a Save button.
Works through a multi-step taskA "Step 2 of 4" indicator, with Back and Continue always in the same place.
Reads a statusThe same status words and colours for the same thing everywhere (Active, Suspended, Card lost), always with text, never colour alone.
Edits a recordOne rule for when editing happens in a dialog, inline or in a side panel, so the same kind of edit never opens three different ways.
Leaves with unsaved changesOne standard "You have unsaved changes" save bar and leave-confirmation, so work is never lost silently.

Accessible and usable by default

Built into every component

Written for people, not systems

Why the look still matters: perceived value

Customers judge software before they use it. A platform that looks bespoke, calm and consistent reads as mature, secure and worth its price; one that looks assembled from defaults reads as cheap, however good the engineering. The design system gives every ITC product that purpose-built finish for free, so it supports sales demos, renewals and pricing, and sets us apart from CBORD, TouchNet and legacy campus-card tools. The team never has to think about it: the look comes with the parts.

Why AI-built screens drift, and how this fixes it

More and more of our UI is written with AI tools like Claude Code. An AI tool is fast, but it only knows what it is told. With no ITC rules, every session invents its own answers to the questions above.

TodayWith the design system
Every screen decides for itself where buttons go, how errors show and how deletes are confirmed.Decided once. Each pattern above is written down as a spec with ready-made markup.
Claude Code doesn't know our conventions, so it guesses, differently each time.Claude Code reads our rules first. Each product repo tells it to follow the system; before building a table or a dialog it reads that spec and uses our part.
Products drift apart. AVRO, GoPrint and the mobile app solve the same task differently.One shared package installed in every product, so the same task works the same everywhere.
Inconsistencies are found late, by eye, screen by screen.Rules are checked automatically, and anything the system doesn't cover yet is flagged in the code instead of invented.

What it looks like for a developer using Claude Code

What we get

OutcomeWhat it means
Predictable for usersThe same task works the same way in every screen and product, so less training, fewer support calls and fewer mistakes.
Accessible by defaultKeyboard, screen-reader and contrast requirements come with each part, not as a retrofit before an audit.
Faster deliveryDevelopers and AI tools assemble screens from finished parts, with no design debates about where a button goes.
One product familyAVRO, the mobile app, GoPrint and future products behave and read as one company.
Perceived valueA bespoke, consistent finish that supports demos, renewals and pricing.
One source, kept currentEvery rule and part lives in one place. Improve it once, and every product gets it with a version update.

How it stays current and grows

1

Change it once

A pattern, part or rule is updated in the design system.

2

Build checks it

Specs, the gallery and the AVRO One preview regenerate, and rule breaks fail the build.

3

Review and release

The design owner reviews it; it ships with a version number and changelog.

4

Products update

Teams bump the version. AI tools follow the new rules on their next task.

New patterns when teams need them

When a screen needs something the system doesn't cover, developers and AI tools flag it in the code (DS-GAP) instead of inventing it. The design owner adds the pattern, and it appears in the gallery and preview automatically.

See it: start with Preview → AVRO One.

Being defined next: the editing pattern (dialog, inline or side panel) and the unsaved-changes standard.

Open decisions: final brand colour sign-off, how Angular apps load the icons, and "client" vs "patron" in product copy.

Who Is This For?

Three groups use the design system every day. AI agents come first, because they write most of our UI and follow the system most literally.

A Markdown file set that AI tools read, kept up to date automatically

Everything in this gallery also exists as plain Markdown files, written and structured for AI tools like Claude Code to read. Every product repo points its AI at them, so the rules travel with the code.

The files

  • AI-RULES.mdShort, strict rules. Loaded into every product repo's Claude instructions, so every session starts with them.
  • components/*.mdOne spec per component: when to use it, the markup to copy, its classes, behaviour, and do's and don'ts.
  • DESIGN.mdPrinciples and foundations: the why behind every rule.
  • docs/*.mdSpacing, icons, tone of voice, brand, and how we build on Tailwind.
  • CHANGELOG.mdWhat changed, in plain English, so agents and people see what's new.

How they stay up to date

Turning it on in a product repo

Two lines in the repo's CLAUDE.md:

All UI MUST follow @node_modules/@itc/avro-arrow-design-system/AI-RULES.md
Before building any UI, read the matching spec in node_modules/@itc/avro-arrow-design-system/components/.

AI agents

The first and most important audience. Most of our UI is now written with AI tools like Claude Code, and an AI tool only knows what it is told. The design system is what it is told.

Engineers & product owners

For the people who build and shape the products.

QA

For the people who check that it works.

Our Stack & Why

Every tool in this system was chosen on purpose. Each choice says why, what else we considered, and what it gives us. Anything not listed here needs a design-system change request.

Tailwind CSS v4

Reviewed Oct 2026

Why we chose it

One theme file becomes both the values and the class names, so there is no second place for styles to drift. AI tools like Claude Code write Tailwind fluently, so pairing it with our tokens makes their output consistent.

What we considered

Angular Material (looks like Google and is hard to make our own), Bootstrap (generic, heavy overrides), and SCSS or CSS Modules (every team and AI session invents its own names).

What it gives us

AI-written screens that match ours, small bundles, and off-system values that stand out in review. The trade-off, long class lists, is solved by our ready-made component classes.

Phosphor icons, Fill weight

Reviewed Oct 2026

Why we chose it

A large, MIT-licensed set with a true filled version of every icon, used exactly as Phosphor ships it.

What we considered

Lucide (outline only, and faking filled icons broke several), Font Awesome (heavier, needs attribution) and Heroicons (too few icons for campus meanings).

What it gives us

A friendly, bespoke look, one icon per meaning, and nothing custom to maintain.

Outfit typeface

Reviewed Oct 2026

Why we chose it

One friendly, geometric typeface for everything, including numbers and money.

What we considered

Pairing Inter with Outfit, and plain system fonts.

What it gives us

A warmer, more recognisable product and one font to load. The trade-off: Outfit has no fixed-width digits, so numbers in columns are right-aligned.

Angular CDK and Angular Aria

Reviewed Oct 2026

Why we chose it

Proven keyboard, focus and screen-reader behaviour for menus, dialogs, tabs and lists, maintained by the Angular team.

What we considered

Writing that behaviour ourselves, or adopting a full component library with its own look.

What it gives us

Accessibility we don't have to reinvent, while the styling stays ours.

Markdown files for AI tools

Reviewed Oct 2026

Why we chose it

AI tools read plain files in the repo directly, and the build keeps them in step with the code.

What we considered

Wiki pages and Figma-only specs, which AI tools can't read reliably or which drift from the code.

What it gives us

AI output that follows our rules from the first prompt, and docs that can't fall out of date.

Also used: @tailwindcss/postcss to plug Tailwind into each app's build, and Google Fonts to load Outfit (self-hosting is an open decision).

AVRO One

Open full screenSource: gallery/demo/index.html

Uses 163 of 166 component classes.

Branding & Logos

ITC Systems is the master brand; each product has its own logo in the same family.

Logo set and files pending

ITC Systems

Master brand: marketing, login footer, legal, emails
ITC Systemsitc-systems-full-light.svg

Full · on light

ITC Systemsitc-systems-full-dark.svg

Full · on dark

Iitc-systems-mark.svg

Mark only

AVRO One

Admin / CRM app shell
AVRO Oneavro-one-full-light.svg

Full · on light

AVRO Oneavro-one-full-dark.svg

Full · on dark

Aavro-one-mark.svg

Mark only

AVRO Portal

Parent & Student Portal, AVRO Mobile
AVRO Portalavro-portal-full-light.svg

Full · on light

AVRO Portalavro-portal-full-dark.svg

Full · on dark

Aavro-portal-mark.svg

Mark only

GoPrint

GoPrint admin, release stations
GoPrintgoprint-full-light.svg

Full · on light

GoPrintgoprint-full-dark.svg

Full · on dark

Ggoprint-mark.svg

Mark only

Do

  • Clear space at least the height of the mark on every side.
  • Minimum 20px tall (full) and 16px (mark). Sidebar uses 24px.
  • Product logo top-left of the sidebar header. One logo per screen.
  • Dark variant on bg-brand-950, photos or brand fills.

Don't

  • Recreate a logo in text, CSS or an icon font.
  • Stretch, rotate, recolour, outline, or add shadows or effects.
  • Put a logo in a box or badge, or on a busy background.
  • Combine two product logos into a lock-up without marketing.

Colour

Neutral ink carries ~90% of every screen. AVRO Indigo (matched to the ITC mobile app) is for action, selection, links and focus; its -50 tint carries soft colour. Brand values are placeholders pending marketing sign-off.

brand

50

#f3f2fd

100

#e8e6fb

200

#d3cff7

300

#b3acf0

400

#8c80e6

500

#6c5dd9

600

#5443c6

700

#4636a8

800

#3a2e88

900

#30296b

950

#1f1a47

ink

0

#ffffff

25

#fbfbfd

50

#f6f6f9

100

#eeeef3

150

#e5e5ec

200

#d8d8e1

300

#bbbbc8

400

#8e8ea0

500

#6b6b7e

600

#525266

700

#3d3d4f

800

#2a2a39

900

#1b1b27

950

#0f0f17

accent

50

#ecfdf7

100

#d1fae9

500

#10b981

600

#059669

700

#047857

Semantic tokens — use these in code

surface

canvas

subtle

line

line-strong

fg

fg-muted

fg-subtle

primary

primary-hover

focus

Status

SuccessWarningDangerInfoNeutral

Typography

One typeface, Outfit, for 100% of the UI. Body 15px, tables 14px, weights 400/500/600, sentence case.

The only typeface · font-sans

Outfit $1,280.50 · 214432 · Oct 24, 2026

Headings, body, tables, labels, badges, numbers and money. Hierarchy comes from size, weight and colour, never a second typeface. Outfit has no tabular figures, so right-align numbers in columns.

Outfit weights · Google Fonts specimen

Aa Bb 123

400 regular

Aa Bb 123

500 medium · buttons, links, nav

Aa Bb 123

600 semibold · headings

ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 & ? ! @ $ %

Pairing: headings + body

Patron accounts

Everyone with an account at Seneca Polytechnic. Balances refresh every 15 minutes from AVRO.

Card settings

Lost cards are suspended immediately; a replacement can be issued from the patron record.

Manage card policies

UI and listings, all Outfit

Jordan Lee
ID 300124
$42.50
Priya Shah
ID 300981
$118.00
2xlPatron accounts and balances
xlPatron accounts and balances
lgPatron accounts and balances
mdPatron accounts and balances
basePatron accounts and balances
smPatron accounts and balances
xsPatron accounts and balances
2xsPatron accounts and balances

Iconography

Phosphor, Fill weight, used as-is. Two sizes, one meaning per icon. Full rules: docs/iconography.md.

Weight

Fill for everything. Bold only for simple marks (plus, x, check, dots, carets, arrows). Original SVGs, never edited.

Sizes

size-4 · 16
size-5 · 20

16 inline with text · 20 nav, icon buttons, tiles, alerts.

Colour

currentColor: same as the text beside it · primary when active · status colours for status only.

Set

Phosphor

@phosphor-icons/core 2.1.1 · MIT · phosphoricons.com

Do & don't

Do

Icon + text for actions, 16px, gap-2.

Don't

Use Regular, Light, Thin or Duotone, or edit an icon. Fill (Bold for simple marks) only.

Don't

Use an icon that isn't in the list (it may not fill cleanly), or colour icons for decoration.

Don't

Icon-only primary actions. Icon-only is for secondary actions, with aria-label + tooltip.

Spacing

How much space goes where: page frame, page structure, inside containers, between elements. Generated from docs/spacing.md.

1

Proximity = relationship.

The more related two things are, the less space between them. Label→field is 6px; unrelated sections are 48px apart.

2

Space shrinks as you go in.

Page gutter 32 → card padding 24 → cell padding 16/14 → element gaps 8/6.

3

Only the scale.

4px grid, steps 1 1.5 2 2.5 3 4 5 6 8 10 12 16 (4–64px). No arbitrary values. Prefer gap-* on the parent over margins on children.

Page anatomy

Tinted areas are spacing. Desktop layout, drawn at real size.

h-16 · px-8 · sheet inset py-2 pr-2
p-8 · 32 page gutter

Clients 145 records

Title → description mt-2

pb-8 · 32 header → content

Active clients

2,520

p-6

Active clients

2,520

Active clients

2,520

gap-4
gap-8 · 32 between blocks

Profiles

px-6 pt-6 pb-4
p-6 · 24 card padding
Card content
gap-12 · 48 between sections
Next section

Page frame (desktop apps)

WhereClasspx
Sidebar width (on the canvas)sidebar = w-64 px-3 pt-4256; 12 sides
Nav itemnav-item = h-10 px-3 gap-3, items gap-0.540 tall, 12 sides, 2 between
Nav group labelnav-label = pt-6 pb-224 above, 8 below
Content sheet insetapp-main = py-2 pr-28 from the window edge
Top bartopbar = h-16 px-8 gap-264 tall, 32 sides
Page gutterapp-content = px-8 py-832
Content widthapp-content-inner = max-w-content (settings max-w-settings, forms max-w-form)max 1280 (880, 640)

Page structure

WhereClasspx
Page header → first contentpage-header = pb-832
Title → descriptionpage-description = mt-28
Between sections (unrelated groups)gap-1248
Between blocks in a section (alert → stats → table)gap-832
Between cards in a grid or rowgap-416
Tightly related blocks (filter bar → table)gap-312
Tabs → tab contenttab-panel = pt-624
Main column → side railgap-1040
Section header → its contentsection-header = pb-416

Inside containers

ContainerPaddingpx
Card body / stat cardcard-body = p-624
Card headercard-header = px-6 pt-6 pb-424 / 16 below
Card list rows, list rowspy-416
Settings rowsettings-row = px-6 py-524 / 20
Table cellspx-4 py-3.5; header py-3; footer px-4 py-316 / 14
Modalheader px-6 pt-6; body p-6 (fields gap-5); footer px-6 py-424; 24; 24 / 16
Alertp-4, icon→text gap-316; 12
Dropdown menupanel p-1.5, items px-2.56; 10
Empty statepx-6 py-1624 / 64

Elements

WhereClasspx
Label → control → help textgap-1.56
Between form fields (both directions)gap-4 (modals gap-5)16 (20)
Button groups, filter controlsgap-28
Filter chipsgap-1.56
Icon → textgap-2 (gap-3 in nav and lists)8 (12)
Tabsgap-832
Stat label → number → deltamt-3, mt-112, 4

Touch-first apps: phones, tablets, kiosks (proposed, confirm)

Touch-first, single column: gutters px-4 on phones, px-6 from sm; cards p-5; blocks gap-6; stacked full-width buttons gap-2; controls h-12 (btn-lg, input-lg).

Don't

  • Add margins to children *and* gaps to the parent. Pick gap-*.
  • Put padding on both a card and its only child.
  • Use space-y-* across mixed content. It breaks when items are hidden.
  • Squeeze spacing to fit more data. Use a denser component (btn-sm, icon-btn-sm, the 14px table) instead.

Radius & Elevation

Soft, layered, low contrast. Flat beats shadowy.

xs
sm
md
lg
xl
shadow-xs
shadow-sm
shadow-md
shadow-lg
shadow-xl

Component

App Shell & Nav

Frame for desktop apps: sidebar navigation, top bar and the content sheet. Spec: components/app-shell.md.

Default

MB
Page content
Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • aria-current="page" on the active link. Skip-to-content link first in the DOM.
  • Nav icons use the Fill weight (see Icon); the active item turns brand-coloured with no background.
  • Collapses to a 72px icon rail with tooltips; drawer below 1024px.
  • Logo: product SVG from assets/logos/ in sidebar-header (wordmark until it lands; see docs/brand.md).

Do / don't

  • ❌ Dark or gradient sidebars
  • ❌ Filled, coloured active nav items
  • ❌ More than 2 nav levels
  • ❌ Module names in ALL CAPS

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Page & Section Header

Top of every page and every section: title, context, actions. Spec: components/page-header.md.

Page header

Developer detailsclasses and HTML

Section header

Recent activity

Changes in the last 24 hours.

View activity
Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Exactly one <h1> per page; sections are <h2>.
  • Dashboards may open with a greeting in page-description ("Welcome back, Patti. You have 12 cards ready to print.").
  • Actions: at most 1 primary + 2 secondary; put the rest in an overflow menu.

Do / don't

  • ❌ Icons next to page titles
  • ❌ Big coloured banners
  • ❌ Record counts in brackets ("Clients (145)")

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Tabs

Switch between views of the same object. Spec: components/tabs.md.

Default

Tab content
Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Arrow keys move between tabs (Angular Aria tabs / CDK FocusKeyManager).
  • Reflect active tab in the URL.

Do / don't

  • ❌ Pill or filled tabs
  • ❌ Tabs for sequential steps (use a Stepper)

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Card & Stat Card

Groups related content on the sheet. Spec: components/card.md.

Card with list

Profiles

Profiles set which plans, privileges and card design a client gets.

  • Student1,764
  • Employee504
Developer detailsclasses and HTML

Stat cards

Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Headings in cards use real heading levels in order.
  • Box only what needs grouping. Side rails use unboxed sections (Page & section header + List).
  • Stat cards sit in a stat-grid and can be links to the full list.

Do / don't

  • ❌ Cards in cards
  • ❌ Coloured card backgrounds
  • ❌ Shadows on static cards

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Button

Triggers an action. One primary per view region. Spec: components/button.md.

Primary

Developer detailsclasses and HTML

Secondary

Developer detailsclasses and HTML

Ghost

Developer detailsclasses and HTML

Danger

Developer detailsclasses and HTML

Link button

Developer detailsclasses and HTML

Inline link

inside a sentence

12 photos are waiting. Review photos

Developer detailsclasses and HTML

Small

Developer detailsclasses and HTML

Large

touch-first apps only

Developer detailsclasses and HTML

Disabled

Developer detailsclasses and HTML

Loading

Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Native <button>; type="button" unless submitting.
  • Disabled: native disabled attribute; btn styles it. Never fake it with opacity alone.
  • Loading: keep width, show spinner, aria-busy="true", disable.
  • Icon-only: use Icon button (aria-label + tooltip).
  • Order in footers: secondary left, primary right. Destructive never default-focused.
  • Sizes: default 40px everywhere; btn-sm (32px) in tables, toolbars and filter chips; btn-lg (48px) only in touch-first apps (phones, tablets, kiosks).
  • Links inside a sentence use link, not a button.

Do / don't

  • ✅ "Export report" · ❌ "Submit", "Click here", "OK"
  • ❌ Two primaries side by side
  • ❌ Pill-shaped buttons on desktop (radius is rounded-sm)
  • ❌ Re-typing the utilities btn already applies

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Icon Button

Compact action in toolbars, table rows, headers. Spec: components/icon-button.md.

Default

with tooltip

Notifications
Developer detailsclasses and HTML

Small

table rows

Developer detailsclasses and HTML

Large

touch-first apps

Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Always aria-label; add a tooltip on hover/focus.
  • Hit area ≥ 32px on desktop / 44px in touch-first apps.
  • Sizes: icon-btn (40px) by default, icon-btn-sm (32px) in table rows and modal close buttons, icon-btn-lg (48px) only in touch-first apps.

Do / don't

  • ❌ Icon-only for primary actions
  • ❌ Unlabeled icons
  • ❌ A row of icon buttons per table row (use one overflow menu)
  • ❌ Filled or outlined icon buttons (ghost only)

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Dropdown Menu

List of actions from a trigger (overflow, user menu). Spec: components/dropdown-menu.md.

Default

Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Angular CDK Menu (cdkMenuTriggerFor): arrow keys, typeahead, Esc.
  • Destructive items go last, after a divider, as menu-item-danger.

Do / don't

  • ❌ More than ~8 items without groups

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Tooltip

Names an icon-only control on hover and focus. Spec: components/tooltip.md.

Icon button with tooltip

Help
Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Shows on hover and on keyboard focus (:focus-within). The trigger keeps its own aria-label.
  • Angular: CDK Overlay when the tooltip must escape a scroll container.

Do / don't

  • ❌ Tooltips holding information you can't get any other way
  • ❌ More than a few words

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Field (Input, Select, Textarea)

Label + control + help/error. All form controls use this wrapper. Spec: components/field.md.

Text input

Used for receipts and low-balance alerts.

Developer detailsclasses and HTML

Select

Developer detailsclasses and HTML

Error

214432 already belongs to Mat Bloom. Enter a different ID.

Developer detailsclasses and HTML

Textarea

Developer detailsclasses and HTML

Disabled

Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Visible label always; placeholder is an example, not a label.
  • Error text replaces help text; linked via aria-describedby; validate on blur, not keystroke.
  • Required: mark optional fields "(optional)" instead of asterisks.
  • Between fields: gap-4; in modals gap-5 (see docs/spacing.md).
  • Never show a raw native-looking select: always input select. Angular: CDK listbox / Angular Aria combobox for custom selects.
  • Controls are 40px; input-lg (48px) only in touch-first apps (phones, tablets, kiosks).

Do / don't

  • ❌ Floating labels
  • ❌ Red borders without a message
  • ❌ Uppercase or muted-grey labels

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Checkbox

Select one or more independent options; bulk-select rows. Spec: components/checkbox.md.

Checked

Developer detailsclasses and HTML

Unchecked

Developer detailsclasses and HTML

Disabled

Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Whole label is clickable. Indeterminate via el.indeterminate = true + aria-checked="mixed".
  • Group with <fieldset><legend>.

Do / don't

  • ❌ Checkbox for an immediate on/off setting (use Switch)

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Radio & Choice Card

Pick exactly one option from a small set. Spec: components/radio.md.

Radio group

Card
Developer detailsclasses and HTML

Choice cards

Bulk action
Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Native radios: arrow keys move within the group. Always a <fieldset> with a <legend> (visible or sr-only).
  • Choice cards: one sentence each, describing the outcome, not the mechanism.
  • Choice cards sit 3 across from sm up; the selected card is highlighted automatically.

Do / don't

  • ❌ Choice cards for more than 4 options (use a select)
  • ❌ Radios for on/off (use Switch)

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Switch

Turn a setting on or off immediately. Spec: components/switch.md.

On

Developer detailsclasses and HTML

Off

Developer detailsclasses and HTML

Disabled

Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Takes effect immediately; confirm with a toast if the result isn't visible.
  • Space toggles. role="switch" makes screen readers announce on/off.

Do / don't

  • ❌ A switch that needs a Save button (use a checkbox)
  • ❌ Switches in tables

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Filter Bar

Search + filters + view controls above any list or table. Spec: components/filter-bar.md.

Default

Photo: pending
Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Filter popovers: Angular CDK Overlay + listbox; Esc closes, focus returns to trigger.
  • Debounce search 250ms; reflect filters in URL query params.
  • Filter bar → table: gap-3.
  • Order: search, then filters, then a spacer, then view and export. Active filters show as chips followed by Clear all.

Do / don't

  • ❌ Filters in a left sidebar AND top bar
  • ❌ 'Apply' button for simple filters

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Data Table

Primary way to show lists of records. Spec: components/data-table.md.

Default

Profile Status Balance Actions
JL
Jordan Lee

Photo approved

Student Active $42.10
Priya Shah

No photo

Employee Suspended $3.20
Developer detailsclasses and HTML

Bulk action bar

first child of table-wrap while rows are selected

2 selected
Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Use <table> semantics; sortable headers are buttons with aria-sort on the <th>.
  • Bulk-select shows table-bulkbar at the top of the frame.
  • Angular: @angular/cdk/table for virtualisation/large sets.
  • Empty → Empty state inside the frame. Loading → skeleton rows, not spinners.
  • Right-align numbers and money with num so place values line up.
  • Row actions: one icon-btn icon-btn-sm in cell-actions, opening a Dropdown menu.

Do / don't

  • ❌ Uppercase or monospace headers and cells
  • ❌ More than 7 columns by default (let users add columns)
  • ❌ Actions as rows of icon buttons; use one overflow menu

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

List

Rows of related items outside a table: activity, things needing attention, quick actions, key figures. Spec: components/list.md.

Activity / needs attention

  • 24 clients imported

    Student intake · Bulk management

    Alex Morgan

    10:42

  • 12 cards ready to print

    Approved photos are ready for the next batch.

    Open card production
Developer detailsclasses and HTML

Quick actions

Developer detailsclasses and HTML

Key figures

Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Use <ul>/<li> for lists of things; links inside rows describe the destination.
  • Quick actions: max 4, verb + noun.

Do / don't

  • ❌ A list of records that people sort or filter (use Data table)
  • ❌ More than one action list per screen

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Badge & Count

Status or category label. Not clickable. Spec: components/badge.md.

Neutral

Student
Developer detailsclasses and HTML

Status

Active Suspended Card lost
Developer detailsclasses and HTML

Brand and info

Employee New
Developer detailsclasses and HTML

Count

145
Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Text must carry meaning alone (not colour only).
  • Fixed status vocabulary per object (e.g. Card: Active, Suspended, Lost, Expired).
  • count is for numbers in tabs and section headers, never for statuses.

Do / don't

  • ❌ ALL CAPS or monospace badges
  • ❌ Badges as buttons

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Avatar

A person: photo or initials. Spec: components/avatar.md.

Initials

JL
Developer detailsclasses and HTML

Sizes

MB PS
Developer detailsclasses and HTML

No photo

Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Decorative next to a visible name; otherwise give the <img> an alt with the person's name.
  • Initials: first + last name, max 2 letters.
  • Sizes: avatar (40px) by default, avatar-sm (32px) in the top bar and dense lists, avatar-lg (48px) in menus and profile headers.
  • Use avatar-empty when a person has no photo yet.

Do / don't

  • ❌ Square avatars for people
  • ❌ Coloured avatar backgrounds per person

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Icon Tile

A soft square that gives a row, card or setting a recognisable icon. Spec: components/icon-tile.md.

Neutral

Developer detailsclasses and HTML

Tones

Developer detailsclasses and HTML

Small

Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Always decorative: the text beside it carries the meaning.
  • Tone = meaning: warning for things that need attention, success for completed work, brand for actions.

Do / don't

  • ❌ Tiles on buttons or in tables
  • ❌ Status tones used for decoration

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Icon

One icon set, Phosphor Fill, used as-is. Spec: components/icon.md.

Fill

almost every icon

Developer detailsclasses and HTML

Bold

simple marks only

Developer detailsclasses and HTML

Status colours

Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Decorative icons next to text: aria-hidden="true". Icon-only buttons: aria-label + tooltip.
  • Icons inherit currentColor. Never rely on an icon alone for status; pair it with text.
  • Only use icons from docs/iconography.md, one meaning each.

Do / don't

  • ❌ Regular, Light, Thin or Duotone weights, or another icon set
  • ❌ Editing, recolouring parts of, or redrawing an icon
  • ❌ Bold where a Fill icon exists

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Alert

Inline, persistent message about a page or section. Spec: components/alert.md.

Warning with actions

12 photos are waiting for approval

Cards can't be printed for these clients until their photos are approved.

Developer detailsclasses and HTML

Info

3 rows will be skipped

Their IDs already belong to archived clients.

Developer detailsclasses and HTML

Success

Last close-out finished Oct 24, 2026

Developer detailsclasses and HTML

Danger

Couldn't add Jordan Lee

Fix the highlighted field and try again.

Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • role="alert" only for urgent errors; otherwise role="status".

Do / don't

  • ❌ Stacking multiple alerts
  • ❌ Alerts for success after every save (use Toast)

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Toast

Brief confirmation after an action. Disappears on its own. Spec: components/toast.md.

Default

Exported 145 clients to Excel.
Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Auto-dismiss after 4–6s; pause on hover. Never the only place an error is shown.
  • One region per app; newest at the bottom.
  • Toasts appear bottom-right, above the content.

Do / don't

  • ❌ Toasts for errors that need action (use Alert)
  • ❌ "Success!" (say what happened)

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Modal

Focused task or confirmation that blocks the page. Spec: components/modal.md.

Default

Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Angular CDK Dialog: focus trap, Esc closes, focus restored to trigger. Initial focus on the least destructive action.
  • The backdrop covers the whole viewport and blocks the page; clicking it closes (same as Cancel). Page scroll is locked while open.
  • Live: gallery → Demo → Add client.
  • Sizes: modal (512px) for confirmations, modal-lg (672px) for short forms.

Do / don't

  • ❌ Modals over modals
  • ❌ Long forms in modals (use a page)

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Empty State

When a list or view has no data. Spec: components/empty-state.md.

Nothing yet

No clients yet

Import a CSV or add clients one at a time.

Developer detailsclasses and HTML

No results

No clients match these filters

Try a different search or clear the filters.

Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Distinguish 'nothing yet' from 'no results for filters' (offer Clear filters).
  • Place it inside the card or table frame it replaces, with at most one primary action.

Do / don't

  • ❌ Illustrations, clip-art or stock images
  • ❌ "No data"

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Settings

Pages of on/off and pick-one options, one consequence per row. Spec: components/settings.md.

Default

Term close-out

What happens to balances and cards at the end of each term.

Carry balances forward

Unused meal and print balances move to next term instead of expiring.

Email clients before close-out

Clients with a balance get a reminder 7 days before their term ends.

Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • Switches save immediately; confirm with a Toast. Settings needing several fields get their own page or modal.
  • Write the description as the outcome for people ("Clients get a reminder…"), not the mechanism.
  • Settings pages are max 880px wide, with 48px between sections.

Do / don't

  • ❌ A Save button for a page of switches
  • ❌ Settings without a description

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Component

Stepper & Checklist

Show progress through a multi-step task or a list of things to do. Spec: components/stepper.md.

Stepper

Step 2 of 4
Developer detailsclasses and HTML

Checklist

  • Confirm term dates

    Fall term ends Dec 18, 2026.

  • Review graduating clients

    38 clients graduate this term.

Developer detailsclasses and HTML

Guidance

Behaviour & accessibility

  • The "Step 2 of 4" text is the accessible progress; the track is aria-hidden.
  • Checklist state is in the text too ("2 of 4 done" above the list).

Do / don't

  • ❌ Steppers for fewer than 3 steps
  • ❌ Tabs for sequential steps

Classes

Read live from src/theme.css; the same table is generated into the spec.

Class keyclassAVRO component classclassTailwind defaultclassAVRO tokenclassTailwind name, AVRO valueclassOne-off valueHover a class for what it does.
ClassApplies (Tailwind utilities)Also

Tonality

One voice across every product, the website and email. Voice stays the same; tone shifts with the audience and the moment. Full guide: docs/tonality.md.

Voice: a calm, competent colleague

Clear

Plain words, short sentences, one idea each

Not: Jargon, vague, clever

Calm

Steady even when something breaks

Not: Alarmist, shouty, over-apologetic

Helpful

Always point to the next step

Not: Blaming, dead-ends

Precise

Exact numbers, names and times

Not: “Some”, “soon”, “a few issues”

Warm, not cute

Human and respectful; greet people by name on home screens

Not: Jokes, emoji, exclamation marks, hype

Tone by audience

AudienceToneSounds like
Admins · desktop toolsEfficient, precise“Export report” · show IDs, counts, exact states
Parents & students · touch-first appsWarm, reassuring, plain“Your balance is $42.50” · explain outcomes, hide system detail
Walk-up users · kiosksDirect, minimal“Tap your card to release 3 jobs” · one step per screen

Tone by moment

MomentDoExample
SuccessConfirm briefly. No celebration.Card issued to Jordan Lee.
ErrorWhat happened + how to fix. Never blame.Couldn’t save the patron. Check your connection and try again.
WarningConsequence before the action.Suspending this card blocks purchases and door access.
DestructiveName the object and count; verb on the button.Delete 3 cards?
Empty stateWhat this is + how to start.No patrons yet. Import a CSV or add patrons one at a time.
MoneyReassure with facts.$50.00 added. Your new balance is $92.50.
Page introOne sentence: what this is, then what needs attention, linked.Everyone with a card or account at Trent. 12 photos are waiting for approval.
Setting descriptionThe outcome for people, not the mechanism.Clients with a balance get a reminder 7 days before their term ends.

Words we use

UseNot
patronuser, customer, member, cardholder
cardID card, badge, credential
add funds · top-up (record)deposit, load, reload, recharge
suspend / reactivateblock, freeze, disable
sign in / sign outlog in, login (verb)
AVRO One, GoPrint, ITC SystemsAVRO1, Go Print, ITC

Before → after

BeforeError 500: Request failed.

AfterCouldn’t load transactions. Try again in a minute.

BeforeAre you sure?

AfterSuspend Jordan Lee’s card?

BeforeSuccess!!

AfterChanges saved.

BeforeClick here to add money to your account

AfterAdd funds

BeforeNo data

AfterNo transactions this month.

Content rules

  • Sentence case everywhere. Buttons are verb + noun (“Save changes”, not “OK”).
  • Numerals always; thousands separators; currency with symbol and 2 decimals.
  • “Today, 12:41” → “Yesterday” → “Oct 24, 2026”. Relative time only under 7 days.
  • No exclamation marks, emoji, “Oops”, or “please” in errors. Links name their destination.

How We Build on Tailwind

For developers and their AI agents. We use stock Tailwind CSS v4 and add exactly one file: src/theme.css. Import it after Tailwind and every app gets the same tokens, global defaults and component classes. No plugins, no JS config, no wrapper library. Source: docs/tailwind.md.

@import "tailwindcss";
@import "@itc/avro-arrow-design-system/theme.css";

The four layers

LayerIn theme.cssWhat it doesYou write
1. Tokens@theme { … }Our colours, type scale, radius, shadows, fonts. Some keep Tailwind's names with our values (see overrides).Nothing. Use the utilities they create (bg-surface, text-fg-muted, rounded-md).
2. Global defaults@layer base { … }Rules for bare elements: page colours, font (Outfit everywhere), focus ring, tabular numbers, reduced motion.Nothing. They apply automatically.
3. Component classes@layer components { … }btn, input, card, table, badge, modal… built with @apply from the same utilities.class="btn btn-primary". Never re-type the utilities a component class applies.
4. Utilitiesstock TailwindLayout and one-offs: flex, grid, gap-4, w-full, ml-auto, mt-6.Utilities for layout around and between components.

Utilities beat component classes (the utilities layer comes last), so class="btn btn-primary w-full" just works.

Why component classes (and not wrappers)

Tailwind recommends removing duplication with framework components or, for small, heavily repeated pieces, with component classes. ITC products run on different stacks, so a CSS class works everywhere and no wrapper library is needed.

Each component class is defined once, specs and gallery are generated from it, and npm run build compiles it with Tailwind so a broken @apply fails the build.

For AI agents

Add to the product repo's CLAUDE.md (see docs/consuming.md):

All UI MUST follow @node_modules/@itc/avro-arrow-design-system/AI-RULES.md
Before building any UI, read the matching spec in node_modules/@itc/avro-arrow-design-system/components/.

AI-RULES.md tells agents to use component classes, never re-type their utilities, and leave <!-- DS-GAP: … --> when no component fits.

Tailwind defaults we override

Same class name, different value. Watch for these if you know stock Tailwind. AVRO values are read live from the theme.

ClassAVROStock Tailwind v4

New (not in Tailwind): text-2xs, text-md, font-display (Outfit), all colours (ink-*, brand-*, accent-*, status, semantic surface/fg/line…), shadow-focus, ease-out-quint, max-w-content / max-w-form / max-w-prose. Spacing is unchanged (4px base).

Global defaults

Everything in @layer base, read live from the theme.

SelectorSets

Component classes

Every class in @layer components, read live from the theme. Open a component for examples and markup.

Rules for developers

  • Reach for a component class first (components/<name>.md). Compose with utilities only for layout.
  • Need a tweak? Add a utility on the element (btn btn-primary w-full). Don't fork the class.
  • Never define component classes or @theme tokens in an app. Request a design-system change (docs/governance.md).
  • No arbitrary values (p-[13px], text-[#333]) and no stock palette colours (gray-*, blue-*).
  • Don't style bare elements (button { … }) in an app. Global defaults live only in theme.css.

Changing a component

Edit its rules in src/theme.css (under its /* @component name */ marker) → npm run build (regenerates the spec's class table and the gallery theme, compiles with Tailwind) → update the spec's prose and gallery examples if behaviour changed → CHANGELOG.md.

See it working

gallery/demo/ is a product screen built exactly this way: app.css holds only the two @import lines above, and npm run build compiles it with stock Tailwind into avro.css. The build fails if the demo uses inline styles, hex colours, arbitrary values or stock palette colours, so it can only ever show what this system provides. Open the demo

Changelog

What changed in the design system, in plain English, newest first.

Oct 28, 2026

Mat Bloom

Oct 28, 2026

Mat Bloom

Oct 28, 2026

Mat Bloom

Oct 27, 2026

Mat BloomVersion 1.0

What's Next?

Upcoming enhancements to the design system.