Platform style
Checked against 2994ef5 on 2026-11-10 by Capy (review pass: Subagent 124 and fable). The files it covers are unchanged since.
A plain web page renders in niso as it does in a browser: niso doesn’t restyle it. Native controls are native, but colors, type and spacing are the page’s. To look like the platform, a page asks for it.
The meta tag
Section titled “The meta tag”<meta name="niso-platform-style" content="auto">content |
Sheet |
|---|---|
auto |
The platform niso is built for: ios on iOS and iPadOS, android on Android, and web everywhere else (macOS, tvOS, visionOS, watchOS, Linux, headless) |
ios, android, web |
That platform’s, wherever it runs |
off |
None, as without the meta |
The sheet sets the platform’s colors, text styles and margins on the page
(and overflow-wrap: break-word, as native text views wrap). It’s in the
niso-platform cascade layer of the author origin, so every unlayered rule of
the page wins over it: you start native and override what you like. The base
sheets are ios.css, android.css and web.css in
crates/niso-core/ua.
It’s off by default because a plain web page must render as it does in a
browser. The meta holds its sheet the way a <link> holds a stylesheet: the
first niso-platform-style meta in the head counts, change its content and
the sheet follows, remove it and the sheet goes
(prelude.rs). A Svelte
page can switch it at runtime:
<svelte:head> <meta name="niso-platform-style" content={on ? 'auto' : 'off'} /></svelte:head>examples/platform shows the same page with and without it.
Tokens
Section titled “Tokens”The sheet’s values come from --niso-* custom properties, which pages can use
directly. They’re defined inside the meta’s niso-platform cascade layer
(crates/niso-core/src/prelude.rs), so a page has them only while it has the
meta with a value other than off; without it, var(--niso-*, fallback)
gives the fallback. Each takes the platform’s own token where
the host set it and a web value otherwise, so the same CSS works in a browser.
The fallbacks are light-dark() pairs where light and dark differ.
From crates/niso-core/ua/niso.css, read when this site was built.
| Token | Apple platforms | Android | Web fallback |
|---|---|---|---|
--niso-color-text | --apple-system-label | --md-sys-color-on-surface | light-dark(#000000, #ffffff) |
--niso-color-text-secondary | --apple-system-secondary-label | --md-sys-color-on-surface-variant | light-dark(#3c3c4399, #ebebf599) |
--niso-color-accent | --apple-system-tint | --md-sys-color-primary | light-dark(#0066cc, #4da3ff) |
--niso-color-on-accent | --md-sys-color-on-primary | #ffffff | |
--niso-color-link | --apple-system-link | --md-sys-color-primary | light-dark(#0066cc, #4da3ff) |
--niso-color-background | --apple-system-background | --md-sys-color-surface | light-dark(#ffffff, #000000) |
--niso-color-grouped-background | --apple-system-grouped-background | --md-sys-color-surface-container-low | light-dark(#f2f2f7, #000000) |
--niso-color-surface | --apple-system-secondary-grouped-background | --md-sys-color-surface-container-high | light-dark(#ffffff, #1c1c1e) |
--niso-color-fill | --apple-system-tertiary-fill | --md-sys-color-secondary-container | light-dark(#7676801f, #7676803d) |
--niso-color-separator | --apple-system-separator | --md-sys-color-outline-variant | light-dark(#3c3c434a, #54545899) |
--niso-font-large-title | --apple-system-large-title | --md-sys-typescale-display-small | 400 34px/41px system-ui |
--niso-font-title1 | --apple-system-title1 | --md-sys-typescale-headline-small | 400 28px/34px system-ui |
--niso-font-title2 | --apple-system-title2 | --md-sys-typescale-title-large | 400 22px/28px system-ui |
--niso-font-title3 | --apple-system-title3 | --md-sys-typescale-title-large | 400 20px/25px system-ui |
--niso-font-headline | --apple-system-headline | --md-sys-typescale-title-medium | 600 17px/22px system-ui |
--niso-font-body | --apple-system-body | --md-sys-typescale-body-large | 400 17px/22px system-ui |
--niso-font-subheadline | --apple-system-subheadline | --md-sys-typescale-body-medium | 400 15px/20px system-ui |
--niso-font-footnote | --apple-system-footnote | --md-sys-typescale-body-small | 400 13px/18px system-ui |
--niso-font-caption | --apple-system-caption1 | --md-sys-typescale-body-small | 400 12px/16px system-ui |
--niso-font-label | --apple-system-subheadline | --md-sys-typescale-label-large | 400 15px/20px system-ui |
--niso-tracking-large-title | --md-sys-typescale-display-small-tracking | normal | |
--niso-tracking-title1 | --md-sys-typescale-headline-small-tracking | normal | |
--niso-tracking-title2 | --md-sys-typescale-title-large-tracking | normal | |
--niso-tracking-title3 | --md-sys-typescale-title-large-tracking | normal | |
--niso-tracking-headline | --md-sys-typescale-title-medium-tracking | normal | |
--niso-tracking-body | --md-sys-typescale-body-large-tracking | normal | |
--niso-tracking-subheadline | --md-sys-typescale-body-medium-tracking | normal | |
--niso-tracking-footnote | --md-sys-typescale-body-small-tracking | normal | |
--niso-tracking-caption | --md-sys-typescale-body-small-tracking | normal | |
--niso-tracking-label | --md-sys-typescale-label-large-tracking | normal | |
--niso-corner-small | --md-sys-shape-corner-small | 8px | |
--niso-corner-medium | --md-sys-shape-corner-medium | 12px | |
--niso-corner-large | --md-sys-shape-corner-large | 16px | |
--niso-corner-extra-large | --md-sys-shape-corner-extra-large | 28px | |
--niso-safe-top | max(env(safe-area-inset-top), var(--niso-safe-area-inset-top, 0px)) | ||
--niso-safe-right | max(env(safe-area-inset-right), var(--niso-safe-area-inset-right, 0px)) | ||
--niso-safe-bottom | max(env(safe-area-inset-bottom), var(--niso-safe-area-inset-bottom, 0px)) | ||
--niso-safe-left | max(env(safe-area-inset-left), var(--niso-safe-area-inset-left, 0px)) |
.card { padding: 16px; border-radius: var(--niso-corner-large, 16px); background: var(--niso-color-fill, light-dark(#7676801f, #7676803d)); font: var(--niso-font-body, 17px/22px system-ui);}Where the platform tokens come from
Section titled “Where the platform tokens come from”Hosts give pages the platform’s design tokens as custom properties in a host
sheet (niso_dom_set_platform_style), which they replace when the user’s
settings change, so a page follows Dynamic Type or a new wallpaper’s colors
without a restart:
--apple-system-*: UIKit’s semantic colors, named after WebKit’s
-apple-system-* keywords, and the text styles at the user’s Dynamic Type size.
--md-sys-color-*, --md-sys-typescale-* and --md-sys-shape-corner-*:
Material’s token names for the web, in the dynamic (Material You) colors and at
the user’s font scale.
None: the --niso-* tokens fall back to their web values.
A page can read them itself, with a fallback for browsers:
var(--md-sys-color-primary, #6750a4). System settings
covers the rest of what the user’s settings change.
Dark mode
Section titled “Dark mode”Pages follow the system’s appearance through standard CSS:
prefers-color-scheme, color-scheme and light-dark() take the system’s
dark mode, and each native control takes its element’s used color scheme.
The accent is different: accent-color and the AccentColor system color
don’t carry the user’s accent (stylo leaves them out of Servo’s build,
Differences), so use
--niso-color-accent with the meta.
Safe areas
Section titled “Safe areas”A page sits inside the safe area unless it asks for the whole screen, as on the web:
<meta name="viewport" content="width=device-width, viewport-fit=cover">A cover page is laid out under the system bars and keeps its content clear of
them itself. With the meta, use the --niso-safe-* tokens rather than env()
(without it, the host’s own --niso-safe-area-inset-* values below):
header { padding-top: var(--niso-safe-top); }--niso-safe-top is max(env(safe-area-inset-top), var(--niso-safe-area-inset-top, 0px)):
in a browser env() gives the inset; in niso env(safe-area-inset-*) is
always 0 because stylo doesn’t resolve it yet
(repros/stylo-servo-viewport-variants-and-safe-area), so hosts publish the
insets as --niso-safe-area-inset-* in the host sheet.