03 — HTML and CSS
37 HTML files, 38 CSS files. Both follow from the component system in 02__, and CSS carries the estate's alignment discipline further than any other language.
1. HTML — the component's markup half
sg-compute-left-nav.html, verbatim:
<nav class="left-nav" role="navigation" aria-label="Main navigation">
<button class="nav-item" data-view="compute" aria-label="Compute">
<span class="nav-icon">⬡</span>
<span class="nav-label">Compute</span>
</button>
<button class="nav-item" data-view="nodes" aria-label="Active Nodes">
<span class="nav-icon">▣</span>
<span class="nav-label">Nodes</span>
</button>
Six conventions:
- A fragment, not a document. No
<html>, no<head>, no wrapper<template>.SgComponentfetches it and puts it in the shadow root. - 2-space indent — deliberately different from the 4 used in JS and CSS. The narrower indent keeps nested markup readable at the same line width.
- Semantic elements.
<nav>,<button>— not<div onclick>. Every interactive control is a real control, so keyboard and focus work without code. - ARIA on every interactive element.
role,aria-label, andaria-currentset dynamically from JS. data-*is the behaviour hook.data-view="compute"— JS readsbtn.dataset.view. Classes are for styling,data-is for behaviour, and the two never mix. That single rule is worth its own line on the site.- Unicode glyphs instead of icon fonts or SVG sprites.
⬡ ▣— no asset pipeline, no icon dependency, consistent with the no-build-step position in02__§5.
2. The page-level conventions
Beyond components, the estate's HTML carries two site-wide patterns worth documenting:
Programmatically injected chrome. pki.sgit.ai's build injects nav, footers and version badges at build time rather than repeating them per page — "so it cannot drift." Hand-written static HTML plus generated furniture. The site should document the pattern, because it is the reason the sibling sites stay consistent.
The markdown twin. Every URL is also available with the extension swapped, and links inside the markdown point at markdown:
"every page is available as markdown at the same path with the extension swapped, and links inside the markdown point at markdown, so a traversing agent never has to parse HTML."
The mechanism is non-obvious and should be published: these are static files, so the browser will not run JavaScript on them — the rendering happens in a Lambda@Edge function. See 05__ §3, because this is an HTML convention that exists entirely for machine readers.
3. CSS — alignment as a convention
sg-compute-left-nav.css, verbatim:
:host { display: flex; flex-direction: column; height: 100%; overflow: hidden; }
[hidden] { display: none !important; }
.left-nav {
display: flex;
flex-direction: column;
align-items: stretch;
padding: 6px 0;
height: 100%;
background: var(--bg-panel);
border-right: 1px solid var(--border-1);
gap: 2px;
}
.nav-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 3px;
padding: 10px 0;
background: transparent;
Property values are aligned to a column within each rule — exactly the discipline applied to Python attribute annotations and JS object keys. The column is set per rule by its own longest property name, so .left-nav and .nav-item align at different columns. The unit of alignment is the block, not the file.
Other conventions visible here:
:hostfirst, as a single line, establishing the component's own box[hidden] { display: none !important; }as a standing reset — the!importantis deliberate, becausedisplay: flexon:hostwould otherwise beat the attribute- Flexbox for layout,
gaprather than margins - Every colour and surface is a token — no literal hex values in component CSS
- Class names are plain and semantic —
.left-nav,.nav-item,.nav-icon. Not BEM, not utility classes. Shadow DOM makes both unnecessary: the styles cannot leak, so they do not need namespacing
That last point deserves a paragraph on the site. BEM exists to solve a problem shadow DOM removes. Naming conventions that fight the cascade are a cost you only pay if your styles are global.
4. Design tokens
Tokens come from the versioned CDN, adopted per component:
get sharedCssPaths() { return ['https://dev.tools.sgraph.ai/components/tokens/v1/v1.0/v1.0.0/sg-tokens.css'] }
Two naming families are in use, which is an inconsistency to resolve:
| Family | Examples |
|---|---|
| Unprefixed | --bg-panel, --border-1, --text-1 … --text-4, --text-link, --shadow-1, --shadow-2, --warn, --warn-soft, --soon |
--sg- prefixed | --sg-text, --sg-text-muted, --sg-surface, --sg-sp-1 … --sg-sp-4, --sg-transition-fast |
--sgl- prefixed | --sgl-tab-h, --sgl-panel-h |
Three prefixes for one token system. The numbered scales (--text-1..4, --sg-sp-1..4, --shadow-1..2) are a good pattern — a bounded ramp rather than arbitrary values — and the site should publish the ramps as the recommended approach. But pick one prefix before publishing, or document what each family means. 08__ Q3.
5. Theming
The token indirection is what makes theming possible: components reference var(--bg-panel) and never a literal, so a theme is a different sg-tokens.css. Verify before publishing that a dark/light switch actually exists — the token names suggest a dark-first palette (--bg-panel, --text-1..4) and this pack could not confirm a second theme file. 08__ §4.
6. What a linter config would encode
CSS (stylelint): 4-space indent · a custom rule requiring aligned values within a block · no literal colours outside the token file (highest value) · :host first · shorthand consistency.
HTML (htmlhint or a custom check): 2-space indent · every <button>/interactive element has an accessible name · no onclick attributes · data-* present where JS reads dataset · component fragments contain no <html>/<head>.
The two rules that would actually catch defects are no literal colours and every interactive element has an accessible name. The rest is formatting.
This document is released under the Creative Commons Attribution 4.0 International licence (CC BY 4.0).
source: /briefs/04__bash-and-generated-shell.md