Design language
What the portal looks like and why. Every example on this page is the portal's own component, drawn from its own tokens.
Start
Overview
Four rules everything else follows from
- Containers recede, content leads. A box, a rule or a header never competes with the data inside it. Borders are 1px and pale; fills are rare. The data is the darkest thing in a box.
- Colour means something or it is absent. Neutrals are untinted greys. The accent marks the main action and the current location; it is ink unless a facility opts into its own colour. Status colours mark status and nothing else.
- The accent is never a message. Banners and notes use the fixed status colours, so a facility whose colour is red or yellow cannot make a notice look like an error or a warning.
- One loud thing per page. At most one page banner and at most one primary button. If nothing is wrong, the page is quiet.
Every table, token and name on this page is example data. This page is built from the portal's own tokens, Basecoat classes, af- extensions and Vue components, so an example cannot differ from what ships. Use the accent control above to try a facility colour; every example responds, and the theme control in the top bar switches light and dark.
Start
Built on Basecoat
basecoat-css): plain CSS classes that work the same in .astro pages and .vue components. The standing rule is Basecoat-class-first: if Basecoat ships a component, use its class and attributes and re-theme it through the token bridge; extend it with a separate af- class; never redefine a Basecoat class. Basecoat supplies structure, the portal's tokens supply identity.Basecoat components in use
| Basecoat class | As used here | Notes |
|---|---|---|
btn | No variant: the primary action. data-variant="outline": ordinary action. data-variant="ghost": low emphasis (Cancel, row links). data-size="sm" 32px in tables and banners; default 36px; data-size="lg" 40px; data-size="icon" 36px square. | One primary per page. Danger is btn af-danger with data-variant="outline". |
badge | data-variant="outline": a neutral pill label for a category (permission, credential type, read or write). | 20px, 12px text. Never status. |
alert | The box for banners and notes, with af-banner or af-note. | See Banner, note and hint. |
table-container + table | With af-box (frame); af-compact, af-num, af-act, af-sub on top. | Three or more items with the same fields. |
field, input, native-select, textarea, label, checkbox | A label above the control, hint or error below. field with data-orientation="horizontal" for a checkbox. af-narrow caps a field at 360px. | A placeholder never stands in for a label. |
button-group | With af-toggle: a single-choice switch (SegmentedControl). The selected button is ink, not the accent. | A range or filter, not page navigation. |
dialog | Native <dialog> with af-modal (scrim, overlay shadow). ConfirmDialog wraps it; focus starts on Cancel. | Decisions that cannot be undone, or a short form. |
empty | Centred title, sentence and optional action. | A list with nothing in it. |
chart | Through BarChart.vue (Chart.js underneath, loaded only where a chart mounts): neutral --chart-N bars. | See Chart. |
Portal extensions on Basecoat
| Class | Why it exists |
|---|---|
af-danger | Basecoat's destructive variant is a tinted fill; the contract wants a red outline and label, never a fill, so it cannot be mistaken for a red facility accent. |
af-banner / af-note (ok, warn, bad, info) on alert | Basecoat's alert has no status treatments and expects an svg; these add the solid banner fills and tinted notes and lay a mark, text and action in one wrapping row. af-banner slim is the one-line shell bar. |
af-box, af-compact, af-num, af-act, af-sub | The table frame, 32px density, right-aligned numbers, the action cell and secondary cells. |
af-toggle | Fills the selected button-group button with ink rather than the accent. |
af-modal | The scrim and overlay shadow on dialog. |
af-btn-xl | The one 52px call to action on the landing page. |
Bespoke pieces
| Class or component | What it is for | Not when |
|---|---|---|
af-st + af-mark (StatusMark) | A status word with its shape. Tones ok, warn, bad, info, busy, off. | Labelling a category; the mark without a word. |
PageMessage | A page's banner slot: a problem or warning as af-banner, a hint as af-note info. | More than one per page. |
AsyncState | The loading, session-expired, access-denied, error and empty ladder for fetched data. | Hand-written loading or error markup. |
af-kv | A two-column list of label and value for one record. | Comparing several records. |
af-stats + af-stat | A label, a 24px value and an optional 12px line. | A number needing a row of context. |
af-copy (CopyField) | A read-only mono value with a Copy button ("Copied" for two seconds). | Values nobody copies. |
Disclosure, ArmedConfirmButton | An expanding region; a two-step inline confirm. | Hiding the page's main content; anything needing an explanation of what is lost. |
InfoTip / PopoverTooltip | A tooltip on the native Popover API. A documented exception: Basecoat's data-tooltip is clipped by scrolling ancestors. | Anything needed to finish the task; it is not reachable on touch. |
af-nav, af-nav-group, af-nav-label | The sidebar. A documented exception: the shell sidebar (fixed, grouped, count badges, drawer under 860px) is built in Base.astro, because Basecoat's sidebar targets an app-shell layout the portal does not use. | In-page tabs. |
af-page, af-page-head, af-sect, af-help, af-wrap, af-topbar, af-brand, skip-link, af-icon, EntitlementMark | Page structure, shell, icon sizing; the entitlements matrix cell. |
The token bridge
Basecoat's own variables (--primary, --border, --ring, --chart-1 ...) are set from the portal's tokens in global.css, so a colour is changed once. These are their values on this page right now. Try an accent above: --primary follows it.
| Token | Value now | Use | |
|---|---|---|---|
--primary | | The primary button, checked controls and links: the AF accent. | |
--primary-foreground | | Text on a primary fill: the AF on-accent colour. | |
--border | | Basecoat container outlines, from the AF border. | |
--input | | Basecoat control outlines, from the AF strong border. | |
--ring | | The focus border and 3px halo of Basecoat controls. | |
--muted-foreground | | Basecoat secondary text, chart axis labels. | |
--chart-1 | | A chart bar: a neutral step. | |
--chart-2 | | A chart bar on hover. |
How it is wired is in docs/portal-styling.md.
Start
Design tokens
portal/src/styles/global.css.| Layer | What it is | Example | Who may use it |
|---|---|---|---|
| 1. Token | A role, with its value. Colours, shadows, fonts, the type scale and radii are in @theme; spacing, control heights and motion in :root; the dark values in .dark. | --color-af-border, defined once in global.css | Every component and page, by name. Only global.css holds a literal colour. |
| 2. Bridge | Basecoat's own variables, set from the tokens so Basecoat follows the theme. | --border: var(--color-af-border) | Basecoat's stylesheet only. |
| 3. Component | A class that combines tokens: Basecoat's, re-themed through the bridge, plus af- extensions in components.css. | .btn[data-variant="outline"] | Pages. |
border: 1px solid var(--color-af-border); padding: var(--space-16); font-size: var(--text-14);
Douse a token for every colour, size, radius, shadow and duration.
border: 1px solid #1e2a3d; padding: 14px; font-size: 9px;
Don'twrite raw values in a component. Each one is a decision made again, by hand.
Enforcement: a contract test (designContract.test.ts) rejects hex colours, raw font sizes, off-scale radii and shadows, and hand-rolled replacements for Basecoat classes in portal/src. Without it the written system and the shipped portal drift apart.
Visual styles
Colour
Neutrals
| Token | Value now | Use | |
|---|---|---|---|
--color-af-bg | | Page and container background. | |
--color-af-bg-subtle | | Hover rows, read-only fields, selected nav item. | |
--color-af-bg-hover | | Hover on buttons and menu items. | |
--color-af-border | | Container outlines and row rules. | |
--color-af-border-strong | | Anything a person can type in or press. | |
--color-af-text | | All primary text, including links. | |
--color-af-text-secondary | | Labels, column headers, help text. | |
--color-af-text-disabled | | Disabled controls only, never readable text. |
Facility accent
The default is a neutral monochrome theme: the accent is ink, near-black in light mode and near-white in dark. The portal does not give a facility an identity it has not chosen. A deployment may opt in by setting one colour (docs/portal-styling.md has the derivation); the portal derives the rest, so any colour is safe:
- The accent appears only as a fill (primary button, checked control, skip link) or a 3px marker (current sidebar item). It is never a text colour, so a pale facility colour cannot produce unreadable links.
- The on-accent colour is computed: white if it reaches 4.5:1 on the accent, otherwise near-black.
- In dark mode a dark accent is lightened until it reaches 3:1 against the page, unless the deployment supplies its own dark-mode value.
The accent in use
| Token | Value now | Use | |
|---|---|---|---|
--color-af-accent | | A fill or the 3px nav marker. Never text, border or banner fill. | |
--color-af-on-accent | | Text and icons on an accent fill: white if it reaches 4.5:1, otherwise near-black. |
Status
| Meaning | Mark | Token | Banner fill | Use when |
|---|---|---|---|---|
| OK | Reachable | --color-af-ok | Working, linked, valid. | |
| Warning | Expires in 2 days | --color-af-warn | Works for now, or someone must wait or ask. | |
| Problem | Blocked | --color-af-bad | Not working. Also destructive actions. | |
| Information | Maintenance Friday | --color-af-info | A neutral notice or hint. Nothing is wrong. | |
| In progress | Minting proxy | --color-af-text-secondary | none | Waiting on the system. |
| Off | Not linked | --color-af-text-secondary | none | Absent by choice, not an error. "Not linked" is this mark: it never raises a banner, at most a hint. |
Chart colours are Basecoat's --chart-1 to --chart-5, bridged to neutral steps (secondary text, text, strong border, disabled text, border), never status colours. Banner fills and their --color-af-on-banner-* text are the same in both themes; --color-af-bad-text is the only status colour used for text.
Do and don't
Dokeep messages in status colours and actions in the accent. Try the yellow accent above: the warning still reads as a warning because of its shape, title and position.
Don'tfill a message with the accent. Try maroon or yellow above: with maroon it reads as an error; with yellow, every notice looks like a warning.
Dopair every status colour with a shape and a word. Colour alone fails for colour-blind users.
Don'tcolour a name to show its state.
Used on every page. Status marks: Overview, Services, Identities, Tokens, Admin.
Visual styles
Typography
| Specimen | Token | Value now | Use |
|---|---|---|---|
| Sign in | --text-32 | | 32 / 40px, 600. Landing page headline only. |
| Tokens | --text-24 | | 24 / 32px, 600. Page title, one per page. Stat values. |
| No tokens yet | --text-20 | | 20 / 28px, 600. Landing section headings; empty-state title; dialog title. |
| Linked accounts | --text-16 | | 16 / 24px, 600. Section title, card name. |
| claude-desktop-laptop | --text-14 | | 14 / 20px, 400 and 500. Default: body, cells, buttons, inputs. |
| Last used | --text-13 | | 13 / 20px, 400 and 500. Column headers, help text, notes. |
| Updated 2 minutes ago | --text-12 | | 12 / 16px, 400. Badges, nav group labels, chart axes. The minimum. |
| Token | Value now | Use |
|---|---|---|
--font-sans | | IBM Plex Sans: all text and headings. |
--font-mono | | IBM Plex Mono: tokens, hostnames, tool names, commands, reference ids. |
Typeface. IBM Plex Sans for headings and text; headings differ only in size and weight (600). IBM Plex Mono, from the same superfamily, for literal values, so a token string beside its label reads as one typeface in two modes.
- Weights in use: 400 for text, 500 for labels and buttons, 600 for headings. No others.
- Number columns are right-aligned with tabular numerals (
af-num). - Capital I, lowercase l and 1 are drawn differently, which matters for hostnames, token names and dataset IDs.
- Self-hosted and open-licensed (OFL). No letter-spacing is added to text; if a page feels tight, loosen row height and padding instead.
- Help text is capped at 80 characters per line, empty-state text at 52.
| Service | Calls | Errors |
|---|---|---|
| Rucio | 1,284 | 11 |
| AMI | 317 | 0 |
Doset names in the text face and right-align numbers with tabular figures.
| Service | Calls | Errors |
|---|---|---|
| Rucio | 1,284 | 11 |
| AMI | 317 | 0 |
Don'tset whole tables in monospace or use tiny spaced capitals for headers. This is the terminal look the portal is leaving.
Used on every page.
Visual styles
Dimensions
| Token | Value now | Use | |
|---|---|---|---|
--space-2 | | Hairline offsets. | |
--space-4 | | Icon to label inside a badge; between a title and its line. | |
--space-8 | | Icon to label; between related controls. | |
--space-12 | | Section title to content; between a label group and its control. | |
--space-16 | | Cell padding; inside banners and notes. | |
--space-24 | | Page header to content; compact page gap. | |
--space-32 | | Between sections; page margin on wide screens. | |
--space-48 | | Between landing regions. | |
--space-64 | | Landing page rhythm; page bottom. |
| Token | Value now | Use |
|---|---|---|
--control-s | | Small: Basecoat data-size="sm". Row actions inside tables, buttons in banners, segmented controls. |
--control-m | | Medium: the default button, input and nav item. |
--control-l | | Large: data-size="lg"; copy fields. |
There is no token for the 52px extra-large button (af-btn-xl): it is the landing page's Sign in, and nothing else.
Density
| Mode | Table row | Cell text | Section gap | Pages |
|---|---|---|---|---|
| Comfortable | 44px | 14px | 32px | Overview, Services, Identities, Tokens, Usage |
| Compact | 32px | 13px | 24px | Entitlements, Admin |
Operators scan many rows at once, so their pages are compact. Physics users check a handful of items, so theirs are comfortable. A compact table may sit inside a comfortable page for a secondary checklist.
Visual styles
Layout grid
| Viewport | Sidebar | Content margin | Content max width |
|---|---|---|---|
| 1136px and up | 220px, fixed | 32px | 1120px |
| 860 to 1135px | 220px, fixed | 24px | fluid, up to 1120px |
| 600 to 859px | A drawer opened from a 52px top bar | 24px | fluid |
| Under 600px | A drawer opened from a 52px top bar | 16px | fluid |
Linked accounts
Grid certificate
Dostack full-width sections. Tables want width, and the reading order is unambiguous.
Don'tdefault to a row of three equal cards. It gives every item the same weight whether or not it matters.
Page content is 960px wide by default (--af-page-max on .af-page), left-aligned inside the column. A page whose main content is a wide table or chart adds af-wide to lift that cap: Services, Tokens, Usage and Admin do (Entitlements has no .af-page root, so it is already uncapped); Overview and Identities do not.
Wide tables scroll inside their own box, never the page. Used on every signed-in page. The landing page has no sidebar: the same column, text capped at 720px, regions 64px apart.
Visual styles
Border and corner radius
| Token | Value now | Use | |
|---|---|---|---|
--radius-4 | | Popover tooltips, chart indicators. | |
--radius-8 | | Buttons, inputs, nav items, copy fields (Basecoat's rounded-md, --radius minus 2px). | |
--radius-10 | | Containers: table boxes, row lists, banners, notes, fields, empty states. Basecoat's dialog panel is 14px. | |
--radius-full | | Badges (tags) and round icon buttons. Not status pills: status uses the status mark. |
| Token | Value now | Use | |
|---|---|---|---|
--color-af-border | | 1px. Container outlines, row rules. | |
--color-af-border-strong | | 1px. Anything a person can type in or press. |
- Table headers are unfilled. No coloured stripe down one side of a container.
- The empty state (Basecoat's
empty) is the only dashed border, and its content is centred, as Basecoat lays it out. - Focus: Basecoat's controls draw a border and 3px halo from
--ring; the portal's own links, nav items and summaries draw a 2px outline in the text colour, offset 2px. No element shows both.
| Account | Status |
|---|---|
| CERN SSO | Linked |
Dolet the container recede: unfilled header, pale rules, the data is the darkest thing in the box.
| Account | Status |
|---|---|
| CERN SSO | Linked |
Don'tuse heavy outlines, filled headers or a coloured stripe down one side. The frame becomes louder than the content.
Visual styles
Elevation
| Level | Token | Allowed on |
|---|---|---|
| Flat | none | Tables, sections, rows, banners, notes, sidebar, top bar, popover tooltips. Separated by a 1px border, never a shadow. |
| Raised | --shadow-af-1 | The chart tooltip (through Basecoat's --shadow-xl), the open navigation drawer. No scrim. |
| Overlay | --shadow-af-2 + --color-af-scrim | Dialogs. Scrim behind, focus trapped. |
| Token | Value now | Use |
|---|---|---|
--shadow-af-1 | | Raised: the chart tooltip (through Basecoat's --shadow-xl), the open navigation drawer. |
--shadow-af-2 | | Overlay: dialogs. |
--color-af-scrim | | The scrim behind a dialog. |
Level 1: raised
Level 2: overlay
The two swatches show the shadow tokens; the real thing is the dialog (see Dialog and tooltip) and the chart tooltip (hover a bar in Chart). No background blur anywhere. In dark mode a shadow is hard to see, so both also carry a 1px light outline. Basecoat's own small shadows (--shadow-xs, --shadow-sm) resolve to none and its backdrop blur to 0.
Expression
Motion
| Token | Value now | Use |
|---|---|---|
--time-fast | | Hover colour. |
--time-base | | Disclosure chevron, the navigation drawer, a chart's first draw. |
--time-slow | | The sign-in spinner. |
--ease | | Every transition. Starts fast, settles. |
- Only hover colour, a turning chevron, the sliding drawer, a small spinner and a chart's first draw move. Page content does not animate on load.
- With reduced motion requested, every transition and spinner stops and charts draw without animation.
- Two named exceptions are kept on purpose: the particle-track canvas (beside the Overview header and in the landing hero; one still frame under reduced motion) and the gateway diagram on the landing page (runs only on screen, never under reduced motion). Neither sits behind text a person needs to read.
Expression
Icons
- Icons are inline SVG, so they inherit text colour and need no network request or icon font.
- They come from the
@lucide/vueand@lucide/astropackages, pinned inpackage.json; markup is never pasted by hand, so an icon can always be traced and updated. - 16px beside text, 20px in a large button. Lucide's default stroke; no per-icon weights or colours.
currentColor,aria-hiddenwhen decorative. - An icon-only button has an accessible name. Status uses the status mark, not an icon.
Lucide at 16px, and in a large button at 20px
PlusCopyExternalLinkXChevronRightKeyDopair an icon with its word. An icon-only button needs an accessible name.
Don'tuse text glyphs as icons. They change shape between fonts and platforms. An arrow is allowed only inside a sentence.
Content
Writing
| Rule | Write | Not |
|---|---|---|
| Name the thing and the fix | Your grid proxy has expired. Renew it to keep grid services working. | Access denied (403). |
| Buttons are verb + object | Link certificate | Continue |
| Sentence case everywhere | New token | NEW TOKEN, New Token |
| Relative time, exact on hover | Expires in 3 days | 2026-11-13T09:14:00Z |
| No filler | Token created | Your token was successfully created! |
| Say what is lost | Revoke token. Clients using it stop working immediately. | Are you sure? |
| Empty states say what goes here | No tokens yet. Create one for clients that can't sign in with a browser. | Nothing to show. |
| Product words, not internals | Grid certificate | x509 VOMS proxy credential provider |
Internal terms (provider class names, config keys) appear only on the Admin page, where the reader is an operator.
Components
Status mark
StatusMark.The six tones
Doput the fix next to a problem status.
Don'tuse outlined pills for status. In a table they become a column of boxes louder than the names beside them. A tag or pill labels a category, never a state.
Used on: Overview, Services, Identities, Tokens, Admin.
Components
Table
| Name | Status | Created | Last used | Calls, 30 days | Actions |
|---|---|---|---|---|---|
| claude-desktop-laptop | Active | 12 Oct 2026 | 2 hours ago | 1,284 | |
| gemini-cli-workstation | Expires in 5 days | 18 Aug 2026 | Yesterday | 317 | |
| old-notebook | Revoked | 3 Mar 2026 | 4 months ago | 0 |
Compact, for Admin and Entitlements
| User | Service | Calls | Errors | Last call |
|---|---|---|---|---|
| example-user-1 | Rucio | 4,102 | 12 | 1 minute ago |
| example-user-2 | HTCondor | 873 | 0 | 14 minutes ago |
| example-user-3 | AMI | 96 | 3 | 2 hours ago |
- First column is the name, in the text face at normal weight and full ink. It is the darkest thing in the row.
- Numbers are right-aligned with tabular figures (
af-num). Dates and secondary facts are secondary text (af-sub). - Row actions (
af-act) are small buttons at the right edge. More than two go into a menu. - Header is unfilled, 13px, secondary colour and sentence case. Rows are 44px, compact rows 32px.
- Under 600px the table scrolls sideways inside its box; the page does not.
Used on: Tokens, Usage, Entitlements, Admin. Services and Identities are row lists (one bordered list, a rule between rows, a name and a status on each), not tables.
Components
Form field
af-narrow).With help
Shown in your token list. Name the device or client.
With an error
Checkbox, and a note on what it does
Leave this unchecked for a token with your full account access.
Douse a visible label and a sensible default.
Don'tuse the placeholder as the label. It disappears as soon as the person types.
Used on: Tokens (new token), Identities (grid certificate passphrase, keytab upload), Admin (maintenance message).
Components
Copy field
CopyField: a value, and a token with its warning
https://mcp.example-facility.org/mcpmcp_pat_EXAMPLE_8f2c41d7b0a94e6f9c3d5a1e7b2f0a91eThe buttons are live. After copying, the button reads "Copied" for two seconds. A multiline copy field holds a command or snippet. Used on: Overview (endpoint and client snippets), Tokens (new token dialog).
Components
Key-value list and stat
Key-value list (af-kv)
- Status
- Valid
- Subject
/DC=ch/DC=cern/OU=Users/CN=example- VO
- atlas
- Expires
- In 11 hours
Stats (af-stats)
Doset stats as plain numbers in a row, separated by space.
Don'tput each number in its own shadowed card with a coloured value.
Used on: Identities (certificate and account details), Usage and Overview (stats), Admin.
Components
Chart
chart component, which wraps Chart.js, through BarChart.vue. Used for the daily activity on the Usage page.BarChart, example data. Hover a bar for the raised tooltip.
- Bars are
--chart-1(secondary text) and their hover is--chart-2(text): neutral steps, legible in both themes, never status colours. The grid is--border; axis labels are 12px in secondary text in the page font, with flat bars and no gradient or shadow. - A canvas is opaque to assistive technology, so the chart is
role="img"with a summary label and every value is repeated in a visually hidden table. - It draws without animation when reduced motion is requested, and redraws with the new colours when the theme changes. Chart.js and Basecoat's chart script load only on a page that draws a chart, and only when it mounts.
Used on: Usage.
Components
Dialog and tooltip
Dialog
This dialog is live: it opens a real native dialog on a scrim, traps focus, starts on Cancel and closes with Escape.
Two-step inline confirm
For a destructive action too small for a dialog.
Tooltip
- Dialog buttons sit bottom right, with the confirming action last. The confirming button repeats the title's verb. The title is the 20px step.
- A tooltip never holds information a person needs to complete the task. It is not reachable on touch.
- Prefer inline content to a dialog. Do not open a dialog from a dialog.
- A tooltip is flat: page background, strong border, 4px radius. It uses the native Popover API, so a scrolling ancestor cannot clip it.
Used on: Tokens (new, revoke), Identities (unlink, consent), Admin (maintenance mode). Tooltips: badges, dates, icon-only buttons.
Components
Empty state
AsyncState): loading, session expired, access denied, error, empty, then the content.Empty state
No tokens yet
Tokens let clients that can't sign in with a browser, such as Claude Desktop, reach the gateway as you.
AsyncState: loading
AsyncState: session expired
AsyncState: access denied
AsyncState: error
AsyncState: empty
No tool calls in the last 30 days
Once your AI assistant calls tools through this platform, its estimated context usage shows up here.
This is Basecoat's empty, which centres its content under a dashed border; the portal keeps that layout. An empty state may carry the page's primary button, so the page header does not repeat it. The failure states are shown as notes here (inline); a page uses the banner form when the page's own data fails to load. Used on: Tokens, Identities, Usage (no calls in range).
Pages
Page pattern
A signed-in page: Tokens, example data
Tokens
For clients that can't sign in with a browser.
Your tokens
| Name | Status | Last used | Actions |
|---|---|---|---|
| claude-desktop-laptop | Active | 2 hours ago | |
| gemini-cli-workstation | Expires in 5 days | Yesterday |
| Part | Rule |
|---|---|
| 1. Maintenance bar | Only while maintenance is on: the slim information bar above everything. |
| 2. Page header | Title (24px), one line saying what the page is for, and at most one primary button on the right. |
| 3. Banner slot | Empty when nothing is wrong. Otherwise the single most urgent message for this page: a banner for a problem or warning, a hint (a quiet note) for information. Problem beats warning beats hint. |
| 4. Sections | Full width, stacked, 32px apart (24px on a compact page). Each has a 16px title and one main component: a table, a key-value list, a row of stats or a copy field. |
| Sidebar | Same on every page. Accent marker on the current page. |
The example is built from the real page classes (af-page, af-page-head, af-sect, af-nav) and components; the shell's sidebar and drawer themselves live in Base.astro. A data page also shows a problem banner when its own data fails to load.
Pages
Per-page specs
| Page | Job | Primary action | Banner slot | Main components | Density |
|---|---|---|---|---|---|
Landing / | Say what the platform is; sign in. | Sign in (af-btn-xl) | Maintenance bar only | Headline, particle canvas, gateway diagram, benefits | Spacious |
| Overview | Is my access working? How do I connect a client? | None; Copy on the endpoint | In priority order: grid proxy expired (problem); a service misconfigured (problem) or unavailable (warning); a service rejected a linked credential (warning); proxy expiring (warning); a token expiring within 7 days (warning). Then hints: no account linked; N services not linked. | Status rows, stats, copy field, particle canvas in the header | Comfortable |
| Services | Which backends and methods can I reach? | None | Misconfigured (problem), unavailable (warning), a credential rejected (warning, with a re-link action when they share one identity). Hint: N services not linked, with an action when they share one account. | Search, filter, service rows (not a table) | Comfortable |
| Identities | Link accounts and keep the grid certificate valid. | None in the header; each card has its own | Proxy expired (problem) or expiring (warning); a link failed (problem); a link succeeded (ok, clears after 5 seconds). No hint: the rows show what is not linked. | One card per credential, key-value lists | Comfortable |
| Tokens | Create and revoke tokens for clients without a browser. | New token | A token expires within 7 days (warning); a revoke failed (problem) | Table, dialog with copy field | Comfortable |
| Usage | What have my clients been doing? | None; range control | None | Stats, bar chart, table per service | Comfortable |
| Entitlements | Which group grants which permission? | None | None | Compact matrix table | Compact |
| Admin | Platform state across users; operators only. | None; maintenance toggle in its section | Tool configuration issues (warning) | Compact tables, status marks | Compact |
A data page also shows a problem banner when its own data fails to load.
State rules that hold on every page
- Not linked is off, not wrong. The off mark, the words "Not linked", a quiet row. It never raises a warning or problem banner; at most a hint. A service that needs no credential (the gateway's own) is never counted as unlinked. The Identities page needs no hint: its rows show the state and the action.
- Credential rejected is a warning, not "Not linked". The user did link, and the backend refuses what they set up. Warning mark, the words "Credential rejected", a row at full ink (never the quiet treatment), one sentence ("Your linked <identity> was rejected. Re-link it to use this service.") and a re-link action that names the identity and goes to its card. It raises a warning banner, ranked below misconfigured and unavailable and above a not-linked hint. It sorts with the things that need attention and is never counted as not linked. Its "Powered by" line shows no link state.
- A grid proxy warns only when it cannot renew itself and has under 1 hour left. An auto-renewing proxy never shows as expiring.
- Misconfigured and Unavailable differ. Misconfigured needs an operator: problem mark and a reference id to quote. Unavailable is temporary: warning mark, nothing to do but wait.
- Access required needs an administrator: warning mark, inline detail, no banner. A row shows its identity's link state only where that is the actionable fact.
- Maintenance is the slim information bar above the page, on every page including the landing page, not the page banner.