Skip to content

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.

<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.

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.

TokenApple platformsAndroidWeb fallback
--niso-color-text--apple-system-label--md-sys-color-on-surfacelight-dark(#000000, #ffffff)
--niso-color-text-secondary--apple-system-secondary-label--md-sys-color-on-surface-variantlight-dark(#3c3c4399, #ebebf599)
--niso-color-accent--apple-system-tint--md-sys-color-primarylight-dark(#0066cc, #4da3ff)
--niso-color-on-accent--md-sys-color-on-primary#ffffff
--niso-color-link--apple-system-link--md-sys-color-primarylight-dark(#0066cc, #4da3ff)
--niso-color-background--apple-system-background--md-sys-color-surfacelight-dark(#ffffff, #000000)
--niso-color-grouped-background--apple-system-grouped-background--md-sys-color-surface-container-lowlight-dark(#f2f2f7, #000000)
--niso-color-surface--apple-system-secondary-grouped-background--md-sys-color-surface-container-highlight-dark(#ffffff, #1c1c1e)
--niso-color-fill--apple-system-tertiary-fill--md-sys-color-secondary-containerlight-dark(#7676801f, #7676803d)
--niso-color-separator--apple-system-separator--md-sys-color-outline-variantlight-dark(#3c3c434a, #54545899)
--niso-font-large-title--apple-system-large-title--md-sys-typescale-display-small400 34px/41px system-ui
--niso-font-title1--apple-system-title1--md-sys-typescale-headline-small400 28px/34px system-ui
--niso-font-title2--apple-system-title2--md-sys-typescale-title-large400 22px/28px system-ui
--niso-font-title3--apple-system-title3--md-sys-typescale-title-large400 20px/25px system-ui
--niso-font-headline--apple-system-headline--md-sys-typescale-title-medium600 17px/22px system-ui
--niso-font-body--apple-system-body--md-sys-typescale-body-large400 17px/22px system-ui
--niso-font-subheadline--apple-system-subheadline--md-sys-typescale-body-medium400 15px/20px system-ui
--niso-font-footnote--apple-system-footnote--md-sys-typescale-body-small400 13px/18px system-ui
--niso-font-caption--apple-system-caption1--md-sys-typescale-body-small400 12px/16px system-ui
--niso-font-label--apple-system-subheadline--md-sys-typescale-label-large400 15px/20px system-ui
--niso-tracking-large-title--md-sys-typescale-display-small-trackingnormal
--niso-tracking-title1--md-sys-typescale-headline-small-trackingnormal
--niso-tracking-title2--md-sys-typescale-title-large-trackingnormal
--niso-tracking-title3--md-sys-typescale-title-large-trackingnormal
--niso-tracking-headline--md-sys-typescale-title-medium-trackingnormal
--niso-tracking-body--md-sys-typescale-body-large-trackingnormal
--niso-tracking-subheadline--md-sys-typescale-body-medium-trackingnormal
--niso-tracking-footnote--md-sys-typescale-body-small-trackingnormal
--niso-tracking-caption--md-sys-typescale-body-small-trackingnormal
--niso-tracking-label--md-sys-typescale-label-large-trackingnormal
--niso-corner-small--md-sys-shape-corner-small8px
--niso-corner-medium--md-sys-shape-corner-medium12px
--niso-corner-large--md-sys-shape-corner-large16px
--niso-corner-extra-large--md-sys-shape-corner-extra-large28px
--niso-safe-topmax(env(safe-area-inset-top), var(--niso-safe-area-inset-top, 0px))
--niso-safe-rightmax(env(safe-area-inset-right), var(--niso-safe-area-inset-right, 0px))
--niso-safe-bottommax(env(safe-area-inset-bottom), var(--niso-safe-area-inset-bottom, 0px))
--niso-safe-leftmax(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);
}

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.

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.

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.

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.