App design guide · v1.0
Gridline Development · Product UI

App design guide

How to build Gridline software that looks like Gridline. Tokens, type, components with every state, maps, charts and motion, shown in both themes. The CSS running on this page is the CSS in the kit, so what you see is what you get.

Version 1.0 · September 2026Pairs with brand guidelines v1.1Dark is the primary theme
Reference screen

What a finished screen looks like

One screen built only from the parts in this guide. Use it to check your own: same density, same hierarchy, same restraint. Flip it to light with the switch under it. All names and figures are example data.

Pipeline/All sites
Sep 19, 2026GR
Pipeline

All sites

6,400Acres under control
1,850MWIn development
5Active sites
3In queue
MarketStageAcresMWkV
Site 01GeorgiaIn queue1,240300230
Site 02GeorgiaUnder control860250230
Site 03AlabamaEntitled2,100600500
Site 04TexasUtility1,450400345
Site 05TexasAt risk750300138
A pipeline view: top bar, sidebar, page title with one primary action, a stat bar, a table and a detail panel. The map carries the amber node, so the selected row uses the quiet ink rule.
Start

Set up in three steps

  1. Load the three fonts, then tokens.css, then gridline-ui.css. Both stylesheets are plain CSS with no build step.
  2. Put class="gl" on the app root. Dark is the default. Add data-theme="light" to the root, or to any wrapper, for the light theme. Leave it off and the app follows the operating system.
  3. Build with the semantic tokens and the gl- classes. When you need something that is not here, make it from the same parts: hairlines, square corners, caps labels, mono figures.
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Archivo:wght@500;600;700&family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@400;500;600&display=swap">
<link rel="stylesheet" href="tokens.css">
<link rel="stylesheet" href="gridline-ui.css">

<body class="gl">                      <!-- dark, or the system setting -->
<body class="gl" data-theme="light">   <!-- light -->

Prefer to self-host? The same font files are in the brand package under fonts/ as WOFF2. Using Tailwind? Load tokens.css, then add tailwind.preset.js to your config: every color in it points at a CSS variable, so the theme switch keeps working.

// theme switch: "dark", "light", or null to follow the system
function setTheme(t) {
  const root = document.documentElement
  if (t) root.dataset.theme = t
  else delete root.dataset.theme
}
Start

Five rules

The brand should read like infrastructure capital: credible, precise, quietly confident. When a choice is unclear, pick what an infrastructure fund would do, not what a software company would do. In product UI that comes down to five rules.

  1. Near-black and paperDark is primary: a navy-950 ground with navy-900 panels. Light is paper with white panels. Blue is a cast in the black, never a panel color.
  2. One amber markAmber means "here": the site on a map, the current stage, the selected record. One per view. The logo and the focus ring do not count. Never an amber button.
  3. Square, flat, hairlineNo rounded corners, no shadows, no gradients. Depth is one step of surface and a 1px line. The node is the only round thing.
  4. Figures are measuredEvery number, unit, coordinate, ID and timestamp is IBM Plex Mono with tabular numerals, right-aligned in tables.
  5. Plain wordsLabels say what the thing is. Buttons say what happens. Claim only what is secured.
Foundations

Color

Build with the semantic tokens. They switch with the theme, so one layout works in both. Reach for a primitive only when a map or chart has to name an exact color.

Semantic tokens

TokenDarkLightUse
groundnavy-950paperApp and page background.
ground-deepnavy-950paper-deepAlternate band, sidebar on light, table zebra if ever needed.
surfacenavy-900whitePanels, cards, inputs, table bodies.
surface-raisednavy-800whiteMenus, dialogs, toasts, tooltips. Always with a rule-strong border.App
inkwhitenavy-900Text, icons, solid buttons.
ink-mutedsteel-300slate-600Labels, captions, secondary text, inactive nav.
ink-disabledsteel-500fog-400Disabled text and placeholder hints. Exempt from contrast rules, so never use it for anything that matters.App
ink-inversenavy-950whiteText on a solid ink fill (primary button, checked box).App
ink-hoverfog-200navy-800Hover fill of solid ink controls.App
hairlinenavy-700fog-200Dividers, panel borders, table rules. Decorative only.
rule-strongsteel-500fog-400Control borders and any border that carries meaning (3:1).
nodeamber-500amber-500The node: the one amber mark in a view. Selected row, current stage, the site on a map.
accent-textamber-500amber-700The one amber word or figure in a view.
focusamber-500amber-700Focus ring: 2px solid, 2px offset, every interactive element.App
dangerred-400red-700Errors, destructive actions, at-risk flags.App
positiveparcel-greenparcel-green-deepConfirmations and completed states. Shares the parcel green.App
infoline-blueline-blue-deepNeutral notices and links inside running text. Shares the line blue.App
lineline-blueline-blue-deepTransmission lines on maps and diagrams.
parcelparcel-greenparcel-green-deepControlled parcels on maps and diagrams.
fill-hoverrgba(255,255,255,.05)rgba(13,27,46,.05)Hover wash on rows, nav items, ghost buttons.App
fill-selectedrgba(255,255,255,.08)rgba(13,27,46,.07)Selected row or item.App
scrimrgba(6,17,32,.72)rgba(6,17,32,.56)Behind dialogs.App

Primitives

Eighteen brand colors and two reds added for product UI. The brand has no red, and an app needs one for errors: these two are muted to sit with navy and are not for marketing.

navy-950#061120
navy-900#0D1B2E
navy-800#14263D
navy-700#2B4463
steel-500#5B7089
steel-300#9CB0C4
slate-600#46586C
fog-400#7B8794
fog-200#C9D1D9
paper-deep#E9E6DF
paper#F4F2ED
white#FFFFFF
amber-500#F8B135
amber-700#8A5A0B
line-blue#6FA8DC
line-blue-deep#2F6DA8
parcel-green#7FA98A
parcel-green-deep#3E6B4B
red-400#E8867AApp only
red-700#A33A2EApp only

The amber budget

  • Amber means "here". The site on a map, the current stage on a timeline, the selected record in a list. One per view.
  • If a map is in view, the map gets it. Lists beside it fall back to the quiet selection: a 2px ink rule. That is what gl-table--quiet is for.
  • What does not count. The logo node and the keyboard focus ring.
  • What is never amber. Buttons, links, headings, icons, badges in bulk, backgrounds, charts as a series color.
  • On light grounds amber text uses accent-text, which switches to amber-700. amber-500 on paper is a mark, never text.

Status color

  • Stage is shown by form, not color. The five-step meter and a mono label carry it. A table of twenty sites stays monochrome.
  • Color is for exceptions. danger for errors and at-risk flags, positive for a confirmation that just happened. Both always come with a word.
  • info and positive share the map colors. Blue is transmission, green is controlled land. Keep them rare in chrome so they keep that meaning.

Contrast, measured

WCAG 2.1 ratios, computed from the hex values. Text needs 4.5:1, large text and meaningful borders need 3:1.

PairRatioGood for
ink on groundDark · 18.9:1All text, AAA
ink on surfaceDark · 17.3:1All text, AAA
ink-muted on groundDark · 8.5:1All text, AAA
ink-muted on surfaceDark · 7.8:1All text, AAA
ink-muted on surface-raisedDark · 6.9:1All text
ink-inverse on inkDark · 18.9:1All text, AAA
accent-text, focus on groundDark · 10.2:1All text, AAA
danger on groundDark · 7.3:1All text, AAA
danger on surfaceDark · 6.7:1All text
positive on groundDark · 7.2:1All text, AAA
info on groundDark · 7.5:1All text, AAA
rule-strong on groundDark · 3.7:1Large text, borders, icons. Not body text.
rule-strong on surfaceDark · 3.4:1Large text, borders, icons. Not body text.
hairline on groundDark · 1.9:1Decoration only. Never text or a meaningful border.
ink on groundLight · 15.5:1All text, AAA
ink on surfaceLight · 17.3:1All text, AAA
ink-muted on groundLight · 6.5:1All text
ink-muted on ground-deepLight · 5.9:1All text
ink-inverse on inkLight · 17.3:1All text, AAA
accent-text, focus on groundLight · 5.3:1All text
danger on groundLight · 5.9:1All text
danger on surfaceLight · 6.6:1All text
positive on groundLight · 5.5:1All text
info on groundLight · 4.8:1All text
info on ground-deepLight · 4.3:1Large text, borders, icons. Not body text.
rule-strong on groundLight · 3.3:1Large text, borders, icons. Not body text.
rule-strong on surfaceLight · 3.7:1Large text, borders, icons. Not body text.
hairline on groundLight · 1.4:1Decoration only. Never text or a meaningful border.
Foundations

Type

Three families, three jobs. Archivo labels and titles, always uppercase. IBM Plex Sans is everything people read. IBM Plex Mono is every figure. The marketing scale in the brand book is too large for product UI, so the app has its own, tighter scale.

StyleSpecSampleUse
page-titleArchivo 700
28 / 32 · -0.005em · caps
Site pipelineOne per screen.
section-titleArchivo 700
20 / 24 · caps
InterconnectionGroups of panels, dialog titles.
panel-titleArchivo 600
13 / 20 · 0.06em · caps
Queue positionPanel and card headers.
labelArchivo 500
11 / 16 · 0.16em · caps
Acres under controlField labels, table headers, stat labels.
buttonArchivo 600
12 / 16 · 0.16em · caps
Save siteButtons, tabs, text CTAs.
statArchivo 600
32 / 36 · -0.01em
1,850 MWKPI figures. Units in the same style, smaller.
bodyIBM Plex Sans 400
16 / 26
We begin at the substation and work outward.Reading text: notes, descriptions, help pages. 64 characters a line.
uiIBM Plex Sans 400
14 / 20
Interconnection study under wayControls, table cells, navigation. The app default.
ui-strongIBM Plex Sans 600
14 / 20
Site 01Row titles and inline emphasis.
smallIBM Plex Sans 400
12 / 18
Figures as of September 2026.Help text, captions. The smallest size for sentences.
dataIBM Plex Mono 400
13 / 20 · 0.04em
33.7490° N 84.3880° WFigures in tables, coordinates, IDs. Tabular numerals.
data-smIBM Plex Mono 400
11 / 16 · 0.08em · caps
230 kV · 0.8 mi to POIMap labels, chips, timestamps, meta.
  • Archivo is always uppercase in the app, except inside stat figures. If a title needs lowercase, it is body copy.
  • Wide tracking is for 13px and below. Never letterspace body copy, lowercase, or a page title. Never stack two letterspaced lines.
  • 12px is the floor for sentences. 11px is for mono labels and caps labels only.
  • Tabular numerals everywhere. .gl sets font-variant-numeric: tabular-nums so columns of figures line up.
  • Line length. Reading text stops at about 64 characters. Tables and forms can run wider.
  • Fallbacks. Arial Narrow for Archivo, Segoe UI for Plex Sans, Consolas for Plex Mono. They are in the font stacks already.
Foundations

Layout

Spacing

A 4px base. Tokens are named by pixel value, --sp-16, so nobody has to look one up. The brand names from the design system map onto the same values.

--sp-4Icon to label. Brand name: space-1.
--sp-8Inside chips; label to field. Brand name: space-2.
--sp-12App only. Control padding, table cell padding.
--sp-16Between fields; mobile gutter. Brand name: space-3.
--sp-24Panel padding; desktop gutter. Brand name: space-4.
--sp-32Between panels. Brand name: space-5.
--sp-48Between sections. Also the decorative grid pitch. Brand name: space-6.
--sp-64App only. Page top padding.
--sp-96Marketing sections only. Brand name: space-7.

The shell

Top bar · 56Logo 22 to 24 high
Sidebar
240
64 collapsed
Content · max 1280
24 gutter · 16 on phones
24 between panels
SizeValueUse
--control-sm32pxDense toolbars, table actions.
--control-md40pxDefault buttons, inputs, selects.
--control-lg48pxPrimary action on a form; touch.
--row44pxTable row. 36px in compact tables.
--topbar56pxApp header.
--sidebar240pxNavigation. 64px collapsed.
--content-max1280pxWidest content column.
  • Breakpoints: 640, 960 and 1280. Below 960 the sidebar collapses to a menu and side panels stack under the table.
  • Left-aligned. Titles, forms and tables hang from the left edge of the content column. Nothing is centered except an empty state inside a panel and a dialog on its scrim.
  • One primary action per screen, top right of the page header. Everything else is secondary or a text CTA.
  • Touch. On phones and tablets, controls are at least 44px tall: use gl-btn--lg and 48px inputs.

Surfaces, borders, depth

  • Three levels, no shadows. ground is the page. surface is a panel, an input, a table. surface-raised is anything floating: menu, dialog, toast, tooltip.
  • Hairlines separate. hairline between things that belong together, rule-strong around anything you can type in or click, and under a table header.
  • Floating things get a rule-strong border. Dialogs also get the scrim. That is all the depth there is.
  • The decorative grid is 48px, in hairline, and only ever a fragment: a corner of an empty state, a login screen, a report cover. Never behind a table or form.
Components

Buttons

Four kinds, in order of weight. A screen has one primary button. Labels are a verb and an object in Archivo caps: "Add site", "File request", "Export table".

Sizes: 32, 40, 48
Disabled, and busy
Text CTAOpen site
Sizes: 32, 40, 48
Disabled, and busy
Text CTAOpen site
Primary is solid ink: white on dark, navy on light. Secondary is an outline. Ghost is for toolbars. Danger is an outline until hover, and solid only inside a confirm dialog.
  • Busy is a packet, not a spinner. Set aria-busy="true", change the label to the present participle, and current runs along the bottom edge.
  • The text CTA is the brand's link style: caps label, a short rule, a node. Use it for "go somewhere", and a button for "do something".
  • Never: amber fills, rounded corners, icons without a label in primary actions, two primary buttons side by side.
<button class="gl-btn gl-btn--primary">Add site</button>
<button class="gl-btn gl-btn--secondary gl-btn--sm">Filter</button>
<button class="gl-btn gl-btn--primary" aria-busy="true">Saving</button>
<a class="gl-cta" href="/sites/01">Open site</a>
Components

Forms

Labels sit above the field in caps and are always visible: a placeholder is a hint, never the label. Fields are 40px tall with a rule-strong border. Figures get a mono input and their unit in a fixed box, so nobody types "MW".

MW
Peak load in the interconnection request.
kV
Enter a transmission voltage: 115 kV or above.
MW
Peak load in the interconnection request.
kV
Enter a transmission voltage: 115 kV or above.
Text, select, a figure with its unit, help text, an error, a disabled field.
Checkbox, switch, radio. The tick draws in 200ms. Radios stay round so people recognise them: the one allowed exception to square.
  • Errors say what is wrong and how to fix it, under the field, with aria-invalid on the input and aria-describedby pointing at the message. The square glyph means color is never the only signal.
  • Validate on blur, not on every keystroke. Never clear what the person typed.
  • One column. Forms read top to bottom. Two columns only for short, related pairs such as load and voltage.
  • Two to four options that fit on a line: use a segmented control instead of radios.
  • Buttons go bottom left, primary first, under the last field.
<div class="gl-field">
  <label class="gl-label" for="mw">Requested load</label>
  <div class="gl-unitfield">
    <input class="gl-input gl-input--mono" id="mw" inputmode="decimal">
    <span class="gl-unit">MW</span>
  </div>
  <span class="gl-help">Peak load in the interconnection request.</span>
</div>

<label class="gl-check"><input type="checkbox"><span class="gl-box"></span><span>Rezoning approved</span></label>
Components

Status

A site moves through five stages, in the order the work is done. The meter shows how far along it is without any color, so a long table stays calm. Flags are the exception and get a color and a word.

Utility Under control In queue Entitled Powered
On holdAt riskExecuted
Utility Under control In queue Entitled Powered
On holdAt riskExecuted
Five stages with the meter, then the three flags. Stage names follow the deck timeline: confirm the exact wording with Grant before launch.
StageMeterMeans
Utility1 of 5Capacity screened and the serving utility engaged. No land committed.
Under control2 of 5Options or purchase agreements executed around the point of interconnection.
In queue3 of 5Interconnection request filed and a queue position held. Studies under way.
Entitled4 of 5Zoning, permits and county agreements in place.
Powered5 of 5Delivered to the user, ready to build.

The brand's voice rule applies to data too: "under control", "in queue", "executed" and "entitled" each mean something specific. The app should never let a record claim a stage it has not reached.

Components

Tables

Most of the app is tables. Text columns are left-aligned in Plex Sans. Figures are right-aligned in Plex Mono with the unit in the header, not in every cell. Rows are 44px, or 36px with gl-table--compact.

MarketStagekVPOI miUpdated
Site 01GeorgiaIn queue1,2403002300.8Sep 12
Site 02GeorgiaUnder control8602502301.4Sep 09
Site 03AlabamaEntitled2,1006005000.6Sep 15
Site 04TexasUtility1,4504003452.1Aug 28
Site 05TexasAt risk7503001381.9Sep 02
MarketStagekVPOI miUpdated
Site 01GeorgiaIn queue1,2403002300.8Sep 12
Site 02GeorgiaUnder control8602502301.4Sep 09
Site 03AlabamaEntitled2,1006005000.6Sep 15
Site 04TexasUtility1,4504003452.1Aug 28
Site 05TexasAt risk7503001381.9Sep 02
Click a row to select it. With no map in view, the selected row carries the amber node. Sortable headers show a caret; the sorted one is solid.
  • No zebra stripes, no vertical rules. One hairline under each row, a rule-strong line under the header.
  • Units live in the header. "MW", "kV", "POI mi". Cells hold the figure only, formatted the same way down the column.
  • Seven rows by six columns is comfortable. Beyond that, let the table scroll inside its own wrapper and keep the first column visible.
  • Empty cell: a single hyphen in ink-muted. Never "N/A", never zero unless it is zero.
  • Row actions appear on hover as ghost buttons at the right edge, and are always reachable by keyboard.
Components

Panels and stats

6,400Acres under control
1,850MWIn development

Interconnection

Updated Sep 12
Utility
Placeholder Power
Request
300 MW at 230 kV
Queue position
14
Study
System impact, in progress
6,400Acres under control
1,850MWIn development

Interconnection

Updated Sep 12
Utility
Placeholder Power
Request
300 MW at 230 kV
Queue position
14
Study
System impact, in progress
A stat bar validates scale at the top of a view. A panel groups one subject: a caps title, a mono meta line, a key and value list.
  • Not everything is a panel. A page title, a paragraph and a form sit on the ground. Panels are for things with their own subject and edge: a table, a map, a record.
  • Four stats at most, and only figures someone would quote. The unit is smaller, in the same style, never a separate color.
  • Dividers inside a page use gl-rule: a hairline with a small muted node at its left end.
Components

Feedback

Study results are due Oct 3. The utility will post them to the queue portal.
Option on parcel 07 expires in 14 days. Extend it or close.
Site 06 saved.
Loading sites
Distance to point of interconnection
Study results are due Oct 3. The utility will post them to the queue portal.
Option on parcel 07 expires in 14 days. Extend it or close.
Site 06 saved.
Loading sites
Distance to point of interconnection
Inline alerts, a toast, loading, a tooltip. The attention alert spends the view's one amber mark, so use it rarely.
Documents

Nothing filed yet

Surveys, title work and utility letters for this site will appear here.

Documents

Nothing filed yet

Surveys, title work and utility letters for this site will appear here.

A confirm dialog on its scrim, and an empty state. Destructive confirms name the thing and the consequence, and the safe choice comes first.
  • Toasts confirm, alerts explain. A toast is one short sentence, bottom right, gone in five seconds, never for errors. Anything the person must read or act on is an inline alert next to the thing it is about.
  • Loading. Under one second, show nothing. After that, a hairline with current running along it at the top of the region that is loading. Keep the old content visible until the new content is ready. No spinners, no shimmer.
  • Dialogs are for confirming something destructive or collecting one small thing. Anything bigger is a page.
  • Empty states say what will appear and offer the one action that fills them. This is the one place the 48px grid fragment shows up in the app.
Patterns

Writing data

The app's credibility is in its figures. Format them one way everywhere.

QuantityWriteNotNote
Acreage1,240 ac1240 AcresThousands separator always. Whole acres unless a survey says otherwise.
Power300 MW300mw, 0.3 GWStay in MW below 10,000. A space before the unit.
Voltage230 kV230KVLowercase k, capital V.
Distance0.8 mi.8 milesOne decimal, leading zero.
Coordinates33.7490° N 84.3880° W33.749, -84.388Four decimals, hemisphere letters, in the UI. Signed decimals in exports.
DateSep 12, 20269/12/26In the UI. ISO 2026-09-12 in exports, logs and file names.
Time14:32 ET2:32pm24 hour, with the zone when it matters.
Money$12.4M$12,400,000.00Three significant figures in summaries. Full figures in ledgers.
Range250 to 300 MW250-300MWSpell out "to". The unit once, at the end.
Missing-N/A, 0, blankA single hyphen in ink-muted.
  • Mono labels are uppercase: 230 KV · 0.8 MI TO POI. In sentences, units keep their normal case: "0.8 mi from the substation".
  • Never round up. 1,238 acres is "1,238", not "about 1,250". A figure that is an estimate says so in its label.
  • Every figure has an as-of date somewhere on the screen, in data-sm.
Patterns

Maps

Maps are schematic: a flat ground, a hairline graticule, contour lines where the land matters, and a small fixed vocabulary of marks. Never a satellite basemap by default, never shaded relief, never the stock look of a web map.

SUBSTATION230 KV115 KVSITE 010.8 MI TO POI0.5 MICONTROLLED · 1,240 ACSITE 021 MIN
SUBSTATION230 KV115 KVSITE 010.8 MI TO POI0.5 MICONTROLLED · 1,240 ACSITE 021 MIN
Every layer uses a token, so one style works in both themes. Blue is always transmission, green is always controlled land, amber is always the site.
LayerStyleToken
GroundFlat fill. No basemap labels, roads in hairline only if needed.ground-deep
Graticule1px. Quarter-mile lines at half strength, mile lines at full strength.hairline
Contours1px at 20%, every fourth at 50%. Never filled. Flatten under the site so lines wrap around it.ink-muted
TransmissionSolid. 3px at 500 kV, 2.5px at 345, 2px at 230, 1.25px below. Towers are 8px open squares. Voltage labelled.line
Substation20px open square with a cross, 2px stroke.ink
Controlled land1.5px outline, 14% fill, acreage labelled.parcel
Tap or route1px dashed 4 4 from the site to the point of interconnection.ink-muted
The site7px radius disc with a 6px knockout ring in the ground color. One per map. Other sites are 5px open squares in ink.node
LabelsIBM Plex Mono 11px, uppercase, 1px tracking. Name in ink, measurement under it in ink-muted.ink, ink-muted
Scale and northBottom right, mono: a one-mile bar and "N".ink-muted

Paint values for a MapLibre or Mapbox style, dark theme. For light, swap each hex for the light value of the same token.

{
  "background":    { "background-color": "#061120" },
  "contour":       { "line-color": "#9CB0C4", "line-opacity": 0.2, "line-width": 1 },
  "contour-index": { "line-color": "#9CB0C4", "line-opacity": 0.5, "line-width": 1 },
  "transmission":  { "line-color": "#6FA8DC",
                     "line-width": ["match", ["get", "kv"], 500, 3, 345, 2.5, 230, 2, 1.25] },
  "parcel-fill":   { "fill-color": "#7FA98A", "fill-opacity": 0.14 },
  "parcel-line":   { "line-color": "#7FA98A", "line-width": 1.5 },
  "site":          { "circle-color": "#F8B135", "circle-radius": 7,
                     "circle-stroke-color": "#061120", "circle-stroke-width": 6 },
  "label":         { "text-color": "#FFFFFF", "text-halo-color": "#061120", "text-halo-width": 2 }
}
  • Hover thickens a line by 1px or raises a parcel fill to 24%. Selected moves the amber node to that site; the previous one becomes an open square.
  • Color is never the only signal. Every line and parcel carries a mono label at the zoom where it matters.
  • Satellite is a toggle, off by default. When on, desaturate it to about 40% and overlay navy-950 at 55%, so the marks still read.
Patterns

Charts

MW under control, by quarter
05001,0001,5002,000Q1 25Q2 25Q3 25Q4 25Q1 26Q2 26Q3 261,850
Acres under control, by market
08001,6002,400Q3 25Q4 25Q1 26Q2 26Q3 26TX 2,200AL 2,100GA 2,100
MW under control, by quarter
05001,0001,5002,000Q1 25Q2 25Q3 25Q4 25Q1 26Q2 26Q3 261,850
Acres under control, by market
08001,6002,400Q3 25Q4 25Q1 26Q2 26Q3 26TX 2,200AL 2,100GA 2,100
Example data. Bars are square and muted, with the current period in ink. Lines are 2px with open square points, labelled at the line end instead of in a legend.
  • Series order: ink, line, parcel, ink-muted. Four series is the ceiling; past that, use small multiples.
  • Amber is not a series. It marks one point that matters, the latest value or a threshold crossing, as a node with a knockout ring.
  • Gridlines are hairline, horizontal only. The baseline is rule-strong. No chart borders, no fills under lines, no gradients, no 3D.
  • Labels are mono, 10 to 11px, uppercase for axes. Label lines directly at their right end. Put the unit in the title, not on every tick.
  • Points are open squares. Round is reserved for the node.
Patterns

Motion

Motion tells one story: the grid is energized. Lines draw, current runs, the node switches on. In the app that means quick, quiet transitions and one signature: a packet of current running along a hairline.

TokenValueUse
--dur-fast120msHover, press, focus.
--dur200msMenus, tabs, toggles, checkbox tick.
--dur-draw400msA line or tick drawing on.
--dur-slow600msPanels and rules drawing in on first load.
--ease-outcubic-bezier(.2,.6,.2,1)Default. Things arrive quickly and settle.
--ease-drawcubic-bezier(.6,0,.2,1)Lines that draw: slow start, firm finish.
  • Ease out. Nothing bounces, overshoots or springs. Nothing loops beside text except a loading line.
  • Move 8px at most. Menus and toasts fade and rise 8px. Pages do not slide.
  • Amber moves only at the node. A newly selected site may brighten once and send one ring outward over 600ms. Nothing else glows.
  • The logo intro (grid draws, node flickers on, name lights) is for the sign-in screen or first load, once per session, skippable. Never on every navigation.
  • Reduced motion: tokens.css sets every duration to zero. Show the finished state.
Patterns

Accessibility

  • Contrast. Body text is 15:1 or better in both themes. ink-muted is the quietest text allowed. ink-disabled, hairline and rule-strong are never text.
  • Focus. Every interactive element shows a 2px focus ring at 2px offset on keyboard focus. Never remove it, never restyle it per component.
  • Keyboard. Everything works without a mouse, in reading order. Dialogs trap focus and return it. Escape closes whatever floats.
  • Targets. 32px minimum with a pointer, 44px on touch.
  • Never color alone. Status has a word, errors have a glyph and a message, map layers have labels.
  • Real controls. The kit styles native inputs, selects, checkboxes and radios, so labels, names and states come for free. Keep label for, aria-invalid, aria-describedby, aria-selected, aria-current and aria-busy: the CSS hangs off them.
  • Zoom. Layouts hold at 200% and reflow at 320px wide. Tables scroll inside their wrapper, the page never scrolls sideways.
Patterns

Words in the UI

Write the way a development partner talks to an investment committee. Plain words, short sentences, the fact first.

WhereWriteNot
Primary buttonFile requestSubmit
DestructiveDrop siteDelete, Are you sure?
SuccessSite 06 saved.Success! Your site has been saved
ErrorThe request was not filed. The utility portal timed out. Try again, or file by email.Oops, something went wrong
Empty stateNothing filed yetNo data available
Field helpPeak load in the interconnection request.Please enter your MW here
LoadingLoading sitesHang tight, crunching the numbers
  • Sentence case for sentences, caps for labels. Buttons, tabs, table headers and field labels are Archivo caps. Everything else is sentence case.
  • No exclamation marks, no "please", no "oops", no "unlock", "seamless", "powerful" or "solutions".
  • Name the thing. "Drop Site 05?" beats "Are you sure?". "Option on parcel 07" beats "This item".
  • Same word, same thing. A site is always a site. Pick "drop", "file", "execute" and keep them.
Ship

Before a screen ships

Ship

What is in the kit

FileWhat it is
tokens.cssEvery token as a CSS variable: primitives, both themes, spacing, sizes, motion. Follows the system theme unless told otherwise.
gridline-ui.cssThe reference styles for every component on this page. Plain CSS, gl- prefixed, no JavaScript needed.
tokens.jsonThe same tokens in the W3C design tokens format, for Style Dictionary, Figma plugins or native builds.
tailwind.preset.jsColors, fonts, type scale, spacing and motion as a Tailwind preset. Radius and shadow are locked to none.
Gridline-App-Design-Guide.htmlThis page, self-contained. Opens offline.
../logos, ../fontsIn the brand package: every logo as SVG and PNG, and the three families as WOFF2.

Questions about the brand itself, the logo, photography or the deck: see the brand guidelines PDF in the same package. If something here and something there disagree about product UI, this guide wins. For anything else, the guidelines win.