Cloud Console Own Projects

How the console's pages look, and the rules behind the look. Every value on this page is copied from web/console.css, the console's one stylesheet. The components are drawn with this site's copy of it, plus the rules it lacks, copied into this page's own stylesheet.

The console builds its pages from the pieces in web/src/ui.ts and web/src/form.ts. A page shows what the API answers. It never decides a label, a state's colour, or which buttons to show: each button appears only when the API lists its action.

Light is the default. The switch in the top bar shows the dark theme, and every sample below follows it.

Colour tokens

31, each light and dark

Type sizes

6, from 12 to 22 px

Control height

36 px; 44 px on phones

Icons

28 line drawings

Colour

Each token has a light and a dark value. Dark comes from <html data-theme="dark">, the person's choice in the theme menu (web/src/theme.ts), or, before the script has run and with no choice made, from the system's prefers-color-scheme. The top bar is navy in both themes. One ultramarine accent; the status colours are kept for status only.

The swatch in each column shows that theme's value, whichever theme this page is in. A see-through value is shown over what it sits on.

Surfaces

TokenLightDarkUse
--canvas#f4f5f8#0f1218The page behind everything
--surface#ffffff#171b23Panels, cards, tables, menus, inputs, dialogs
--raised#f7f8fa#1e232dA raised or striped surface: table headers, code blocks, the row under the pointer
--scrimrgb(12 16 26 / 45%)rgb(0 0 0 / 60%)The shade behind an open dialog, and behind the navigation drawer
--edge-shadowrgb(0 0 0 / 22%)rgb(255 255 255 / 16%)The "more this way" shade at the edge of a strip that scrolls sideways (the tabs)

Ink

TokenLightDarkUse
--ink#1c2230#e6e9efText
--muted#5a6272#9ba4b4Secondary text: hints, table headers, breadcrumbs, a summary card's label

Lines

TokenLightDarkUse
--line#e0e3e9#2a303cBorders of panels, cards, tables and rows
--line-strong#c3c8d2#3c4452Borders of controls (buttons, inputs), the breadcrumbs' separator

Accent

TokenLightDarkUse
--accent#3a4ed6#9aa7ffLinks, the primary button, the current tab and navigation entry
--accent-hover#2f40b8#b6bfffThe primary button under the pointer
--accent-ink#ffffff#121738Text on the accent
--accent-soft#eceffd#232a4dA plain button under the pointer, the current navigation entry, a ticked row, a field's focus halo
--focus#3a4ed6#9aa7ffThe focus ring

Semantic

TokenLightDarkUse
--ok#17784a#63d394Success: its chips and the done notice
--ok-soft#e4f4ea#16301fThe fill behind success
--bad#c0262d#ff8f88Danger and failure: failed chips, refusals, field errors, the danger button
--bad-soft#fcebea#3a1a1aThe fill behind a refusal, a failure notice, a danger button under the pointer
--warn#8d5a00#f1be5eWarning: skipped runs, maintenance banners, a value shown once
--warn-soft#fcf1d9#33270fThe fill behind a warning
--info#0b67b0#72b7ffUnder way or for information: queued and running chips, the info banner
--info-soft#e5f0fa#15283dThe fill behind information
--idle#5a6272#a3acbbA neutral state: paused, deleted, disabled
--idle-soft#eef0f3#252b35The fill behind a neutral chip, inline code, the loading skeleton's bars, a hovered row menu

The top bar

TokenLightDarkUse
--bar-bg#1b2235#151a26The top bar's navy, and the band behind the sign-in card
--bar-ink#eef1f8#eef1f8 (as light)Text on the bar
--bar-muted#a9b1c6#a9b1c6 (as light)Quieter text on the bar, the project picker's chevron
--bar-hoverrgb(255 255 255 / 10%)rgb(255 255 255 / 10%) (as light)The project picker's fill, a control on the bar under the pointer
--bar-linergb(255 255 255 / 16%)rgb(255 255 255 / 12%)The bar's bottom edge, the picker's border, the ring round the avatar
--bar-logo#5b6cf0#5b6cf0 (as light)The mark's tile on the bar
--bar-logo-ink#ffffff#ffffff (as light)The mark's bars on the bar

Status: colour and shape

A state is never told by colour alone. Each tone of chip has its own shape before its words, so the chips read apart in grey too. The chip says the API's label; its tone follows the state the API answered (toneOf in web/src/ui.ts).

ToneShapeThe states it showsChips, as drawn
okA filled dotA running queue, a succeeded task, an enabled job, a successful run, a level within its limit, a backup that is fine, a running cronSucceeded Enabled Active
badA filled diamond (a square turned 45 degrees)A failed task or run, a level over its limit, a backup that is stale, failing or off, a stale cronFailed Failed (503) Stale
warnA filled triangleA skipped run, a level near its limit, a backup that never ran, a cron with parts failing or not run yet, an expired invitationSkipped (daily quota) Not run yet
infoA ring open on one sideA queued or running task, a running job run, a backup under wayQueued Retrying (attempt 3)
idleAn open ringA paused queue or job, a deleted task, a disabled person or account, and any state not listedPaused Deleted Disabled

Contrast

The ratios below are worked out from the hex values above, with the WCAG formula. Text needs 4.5:1, large text and the parts of a control 3:1.

Text or markOnLightDarkNote
--ink--surface15.90:114.18:1Body text
--muted--surface6.13:16.87:1Hints, table headers, labels
--muted--canvas5.62:17.47:1Breadcrumbs, a page's own hints
--accent--surface6.48:17.68:1Links and plain buttons; the focus ring is the same colour
--accent-ink--accent6.48:17.75:1The primary button
--accent--accent-soft5.66:16.19:1The current navigation entry, a hovered button
--ok--ok-soft4.82:17.64:1A success chip or notice
--bad--bad-soft5.12:17.10:1A failure chip, a refusal
--bad--surface5.90:17.83:1A field error, the danger button
--warn--warn-soft5.21:18.53:1A warning chip, the maintenance banner
--info--info-soft5.08:17.08:1A queued chip, the info banner
--idle--idle-soft5.37:16.22:1A paused chip
--bar-ink--bar-bg14.00:115.38:1The top bar's text
--bar-muted--bar-bg7.38:18.11:1The top bar's quieter text
--bar-logo-ink--bar-logo4.33:14.33:1The mark's bars: a picture, not text
--line-strong--surface1.68:11.76:1A control's border: under 3:1. Every field has a visible label above it, and the focus ring is the accent.

Typography

One system font stack for everything, and a monospace stack for code and values. No web font is loaded: the CSP lets a page load only the console's own files.

--font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif;
--mono: ui-monospace, "SF Mono", "Cascadia Mono", "Roboto Mono", Menlo, Consolas, monospace;

The type scale

TokenSizeUsed forSample
--text-xs0.75rem, 12 pxChips, the navigation's group titlesQueued tasks 1,234
--text-sm0.8125rem, 13 pxHints, field labels, field errors, table headers, breadcrumbs, a summary card's labelQueued tasks 1,234
--text-md0.875rem, 14 pxBody text, controls, h3Queued tasks 1,234
--text-lg1rem, 16 pxh2, the name in the top bar, a phone card's titleQueued tasks 1,234
--text-xl1.25rem, 20 pxA summary card's value, a dialog's title, h1 on a phoneQueued tasks 1,234
--text-2xl1.375rem, 22 pxh1, the page's titleQueued tasks 1,234

Weights

  • 400: body text.
  • 500: buttons, navigation links, tabs, notices, field errors.
  • 600: headings, field labels, table headers, chips, a summary card's value, the current tab and navigation entry.

Line heights and spacing of letters

  • 1.5: body text, code blocks and text areas.
  • 1.25: h1, which is set 0.01em tighter.
  • 1.35: h2. 1.3: a summary card's value. 1.2: buttons and chips.
  • The navigation's group titles are set 0.02em wider.

Numbers and code

Spacing and shape

Space

TokenValueShownWhere
--space-14 pxGaps inside a header's toolbar, between lines of a list
--space-28 pxBetween buttons in a toolbar; under the breadcrumbs
--space-312 pxBetween summary cards; the gaps of the top bar
--space-416 pxInside a table cell's sides; the top bar's sides; a page's sides on a phone
--space-520 pxA page's top; a panel's top and bottom; between form fields
--space-624 pxA panel's sides; a dialog's padding
--space-832 pxA page's sides on a wide screen; above each h2
--space-1040 pxBelow a page's content

Corners

Shadows

TokenLightDark
--shadow-sm0 1px 2px rgb(20 26 40 / 8%)0 1px 2px rgb(0 0 0 / 40%)
--shadow0 1px 2px rgb(20 26 40 / 8%), 0 8px 24px rgb(20 26 40 / 12%)0 1px 2px rgb(0 0 0 / 40%), 0 8px 24px rgb(0 0 0 / 45%)
--shadow-lg0 2px 6px rgb(20 26 40 / 10%), 0 24px 48px rgb(20 26 40 / 22%)0 2px 6px rgb(0 0 0 / 40%), 0 24px 48px rgb(0 0 0 / 55%)

Sizes

WidthWhat changes
Wider than 960 pxThe left navigation is a column of 248 px (--nav-width) beside the page.
960 px and narrowerThe navigation becomes a drawer behind the menu button, up to 280 px wide, over a scrim.
640 px and narrowerOne column. Table rows become cards, controls are 44 px, the console's name in the top bar is hidden (its mark stays), and a details page keeps its first action and Refresh as buttons, the rest in a ⋮ menu.
Any touch screenControls of 44 px whatever the width (pointer: coarse).

Components

Each is drawn below with the console's classes. Buttons and fields are live: point at them, or reach them with Tab. The top bar, the navigation, the tabs and the page header are pictures, as this site draws the console's screens.

Buttons

KindHow it looksUse it forNever for
PlainAccent words on the surface, a --line-strong border; --accent-soft under the pointerMost actions: Edit, Pause, Rotate, Download a sampleThe one action a form exists for
PrimaryAccent fill, --accent-ink words, a small shadow; --accent-hover under the pointerA form's submit (Create, Save, Sign in), Try again, Copy for a value shown onceAn action that cannot be undone: that is a danger button behind a question
DangerRed words; --bad-soft and a red border under the pointerDelete, Revoke, and the answer in a confirm dialogA safe action, to catch the eye
QuietNo border, no fillRefresh, Cancel, Later, Dismiss this messageThe main action of a page or form
Disabled
At 45% opacity, with a not-allowed pointer and no fill. In the bar of actions over a table, a button that does not apply yet is muted rather than faded.
Busy
While its request runs, the control that sent it is disabled and marked aria-busy="true" (busy() in ui.ts; a form's submit gets the class sending). It keeps full colour, shows a progress pointer and a turning ring after its words, and a second tap sends nothing.
Icons
A toolbar button wears the icon its words name (Refresh, Create, Edit, Pause, Resume, Delete and the rest); the words stay the button's text.

Chips

Succeeded Failed Skipped (daily quota) Queued Paused
Use it for
A state, in the API's own words: a task's, a job's last run, a key's, a usage level.
Never for
An action or a link, or a tone the page works out for itself.

Panels and summary cards

Tasks in queue

12

Tasks running

1

Completed in the last minute

3

Location

Global (Cloudflare)

Type

Push

Task

Schedule time
Oct 22, 2026, 7:33:33 PM
Attempts
0
Attempt deadline
3m
Summary cards
Use them for the few counts at the top of a details page or the dashboard (a queue's: Tasks in queue, Tasks running, Completed in the last minute, Location, Type). Never for navigation: the write-up's link cards do that. Two to a row on a phone.
Panels
Use one for a titled part of a page: a form, a list, label and value pairs. Never for a single line of text, or for a state: that is a chip or a notice.

Tables

NameStateAttemptsLast errorActions
order-1001Queued0—
order-1000Succeeded1—
order-0999Failed1404 Not Found; stopped: 404 is not retried
Wide screens
A table sits in a frame (.table-frame) and scrolls inside it, never the page. Its header row stays in view while a long table scrolls, the row under the pointer is --raised, a ticked row is --accent-soft, and the last column, with the row's ⋮ menu or buttons, stays at the right edge however wide the table is.
Phones
At 640 px and narrower the header row is hidden and each row is a card. Every cell carries its column's name in data-label, shown beside its value. The Name (or Email) cell is the card's title, without a label; the tick box and the ⋮ menu sit in the card's top corners; cells marked extra wait behind Show details at the card's foot. (This site's copy puts each label above its value instead.)
Use it for
Rows of the same kind: tasks, jobs, keys, people, attempts. With no rows, an empty state in its place.
Never for
Laying out a form, or one item's details: those are label and value pairs.

Forms

A task named order-1001 is in this queue, or succeeded or was deleted within the last day

Optional. Letters, digits, _ and -. Left empty, the queue names it. A name is refused while the queue remembers it.

url is required: an http:// or https:// URL

Label
Every field has a visible label above it, tied to its control (form.field() in web/src/form.ts).
Hint
Muted, at --text-sm, under the control, and read with it (aria-describedby).
Field error
The API's message for that field, in red under it; the control's border turns red and it is marked aria-invalid. The page never checks what was typed: it sends it, and shows each fields message at the input its path names (headers.X-Foo goes to headers). The first field in error takes the focus.
Form message
At the form's top, red with a thick left edge and role="alert": a refusal with no field of its own, or Check these: and a list of the messages that have no input. A refusal of a chosen file lists each message with its path in the file, such as tasks.3.url.
File chooser
The text box's frame, its button drawn as the console's own in either theme (::file-selector-button), not the browser's.
Never
A placeholder in place of a label, or a page's own check of the input.

Notices and banners

Revoked shop-app.

The task is running or already done

Notice
One live line per page, kept in view under the top bar while the page scrolls: what was done (ok), or the API's error (bad). A new one replaces the last. It arrives with a short fade and drop, and has a Dismiss button.
Banner
Above the page's content. Warn: one for each maintenance switch that is on, with who switched it and when. Info: a newer build of the console, with Reload and Later; and on a queue's Logs tab, Only failed attempts are logged. Turn on success sampling to keep a share of the successful ones too.
Never for
A field's own problem (that goes beside the field), or anything that must stay on screen after the next action.

Tabs

A picture of a queue page's tabs, Tasks chosen.

How
44 px tall, muted words; the chosen tab in accent at 600 with a 3 px bar under it. A strip wider than the screen scrolls sideways, with a shade at an edge while more is hidden past it; on a phone it runs to the screen's edges, and the chosen tab is scrolled into view. The chosen tab is kept in the address (?tab=).
Use it for
Views of one thing: a queue's Metrics, Logs, Tasks, Configuration and Failed; a job's Logs, Results and Configuration.
Never for
Steps that must be done in order: the job form uses numbered steps with Continue and Back.

The top bar and the navigation

A picture of the console's frame: the top bar, the left navigation with Queues current, and a task page's breadcrumbs and header.

Top bar
Navy (--bar-bg) in both themes, 56 px, kept at the top while the page scrolls. From the left: the menu button (960 px and narrower), the mark and name (a link to the dashboard), the project picker, then the theme menu (System, Light, Dark) and the person's menu: who they are, the version, Your account, Sign out, and Sign out on every device.
Navigation
Grouped: Dashboard, Projects and Usage; then, with a project chosen, Cloud Tasks (Queues), Cloud Scheduler (Jobs) and Project settings (API keys, Secrets, Alerts, Export and import); then Admin (People, Audit log). An entry shows only with its permission: API keys with keys, Alerts with update, People and Audit log for admins. The current one is accent on --accent-soft with a 3 px accent edge at its left. With no project chosen it says Choose a project to see its queues, jobs and settings.
This site
The bar under this page's top bar (.subnav) is the write-up's own navigation, not the console's.

Breadcrumbs and the page header

Breadcrumbs
Muted at --text-sm, a / in --line-strong between the steps, the page itself in ink and not a link (crumbs() in ui.ts). On a phone each link's target grows taller around its line.
Page header
The title (h1), a chip with its state when it has one, then the page's own buttons, flat on the page; a line under it all. On a phone the buttons go to rows of their own under the title.
Never for
Breadcrumbs as the only way back: the navigation is always there.

Empty states and loading

No attempts yet.

Something went wrong on this page. Reload and try again.

Loading the keys…
Empty
The empty icon, then one line that says what would be here and, where it helps, why: No failed tasks. Failed tasks are kept for 7 days. A table with no rows is an empty state instead.
Could not load
The error in place of what could not be loaded, a red left edge, role="alert", and Try again. Never a blank page.
Loading
Until a page's first answer: its words (Loading the keys…) over a pulsing skeleton of rows, marked aria-busy.

Dialogs and values shown once

Revoke shop-app?

Apps using this key are refused from now on. This cannot be undone.

The key shop-app

Copy it now and keep it in the app's settings. You won't see it again: the console keeps only a hash of it.

Confirm dialog
A modal over the scrim, before something that cannot be undone: the question, what it means, Cancel (quiet) and the action (danger). Escape cancels, and the focus goes back to the control that opened it (confirmAction()).
Shown once
A new API key or signing secret: warn colours, the warning, the value in a read-only field and Copy. When the clipboard is not there, the value is selected for the person to copy (The text is selected: copy it from there).
Never for
A question before a safe action, or a value the console can show again.

Icons

Each is a line drawing on a 24-unit grid, in currentColor, 18 px in a button, with a 1.8 stroke and round ends (web/src/icons.ts). An icon is hidden from screen readers: the button or link it sits in carries the words. No icon font and no CDN.

Motion and micro-interactions

TokenValueUse
--easecubic-bezier(0.2, 0, 0, 1)Every transition and the arrivals: quick to start, gentle to stop
--fast120 msHover and focus changes
--slow200 msA notice or dialog arriving; the navigation drawer

A backup has started; it finishes within a few minutes.

Loading the usage…

The ring turns and the skeleton pulses. Point at this box to see the notice arrive again.

What movesHowTiming
A notice arrivingFades in and drops 4 px into place (arrive)--slow, --ease
A dialog openingThe same arrival--slow, --ease
The busy ringA 14 px ring in the button's colour, open on one side, turning (spin) until the answer0.8 s a turn, linear
The loading skeletonIts rows pulse between full and 55% opacity (pulse)1.4 s each way, --ease
HoverButtons, navigation links, icon buttons, table rows, a row's ⋮ button and tabs change their fill, border or words--fast, --ease
Focus on a fieldIts border turns accent, with a 3 px --accent-soft halo (--bad-soft for a field in error)--fast, --ease
The navigation drawerSlides in from the left, over a scrim (960 px and narrower)--slow, --ease
Show details on a phone cardIts chevron turns half a turn--fast, --ease
A sortable columnIts sort mark fades in on hover or focus--fast, --ease

Reduced motion. With prefers-reduced-motion: reduce every animation and transition is off, on elements and on their ::before and ::after: the ring and the skeleton stand still, and notices appear at once.

Accessibility rules the console follows

RuleHow the console keeps itWhere
Visible focusA 2 px --focus ring, 2 px out, on whatever has keyboard focus (:focus-visible); on the navy bar the ring is --bar-ink. A field gets an accent border and a 3 px halo. A tab draws its ring inside itself, since the strip clips its edges. A skip link, Skip to the page, shows when it takes the focus.web/console.css, web/src/shell.ts
44 px targets on phones--control is 44 px at 640 px and narrower and on any touch screen. So are row menus, menu items, navigation links, tick boxes, the buttons in a table, the segmented buttons, icon buttons and the person's menu; a breadcrumb or a link in a table gets a taller target around its line. Check boxes are 22 px on a touch screen.web/console.css
Text onlyPages set text only through textContent, with the small h() helper; ESLint refuses innerHTML, outerHTML, insertAdjacentHTML, srcdoc, document.write, eval and new Function in web/. The CSP allows no inline script or style. A name or URL from the API is always shown as text.web/src/ui.ts, web/src/form.ts, the repo's CLAUDE.md
Labels on inputsEach field has a label for its control, and its hint and error are read with it (aria-describedby). A field in error is marked aria-invalid, a group's message is tied to its fieldset, and the first field in error takes the focus. A value shown once is named with aria-label.web/src/form.ts, web/src/ui.ts
Live regionsA page's notices are one role="status" region, aria-live="polite"; a failure notice is role="alert". The form message, a refused file and a part that could not load are role="alert"; the Copy button's status, the maintenance banners and the new-version banner are role="status".web/src/ui.ts, web/src/form.ts, web/src/shell.ts
Not by colour aloneEvery chip tone has a shape of its own, and the words are the API's label.web/console.css, web/src/ui.ts
Busy, saidA control whose request runs is disabled and aria-busy, so a second tap sends nothing; a loading placeholder is aria-busy.web/src/ui.ts, web/src/form.ts
KeyboardA row's ⋮ menu is a group of plain buttons that Tab moves through (not role="menu", which promises arrow keys). Escape closes a menu and gives the focus back to its button; a menu opens inside the screen. Tabs move with the arrows, Home and End. A confirm dialog gives the focus back to what opened it, and after a row's action redraws the list, the focus goes back to that row's menu button, or the next one.web/src/ui.ts

Built from Cloud Console's main branch (CAP-162), 22 Oct 2026. Example values only: no real keys, emails or ids.