Component
texture
Demo
tokens.css,
base.css and texture.css. No scripts.
Source
Copy both files, or run npx nojsui add texture.
Browser support
Works across current and earlier versions of every major engine.
| Feature | Baseline | Chrome | Edge | Firefox | Safari | Chrome Android | Firefox Android | Safari iOS |
|---|
Usage
.sk-texture is a decorative backdrop: aria-hidden, pointer-events: none,
a negative z-index, and position: fixed so it sits behind everything and
does not scroll with the page.
<div class="sk-texture" data-texture="dots" aria-hidden="true"></div>
data-texture takes dots, grid, or glow.
data-texture | Made of |
|---|---|
dots | One radial-gradient() stop, tiled by background-size |
grid | Two repeating-linear-gradient()s, one per axis, drawn as hairlines |
glow | A single soft radial-gradient() wash anchored to the top |
How it works
The default ink colour is color-mix(in oklab, var(--sk-color-text) 7%, transparent) — not a picked value, a mix. Because it is derived from the text
colour rather than chosen outright, it inverts automatically when the colour
scheme flips: there is no @media (prefers-color-scheme) block in this file,
no second value for dark mode, no [data-theme] override. --sk-color-text
already changes with the scheme, so anything mixed from it changes too.
This generalises past this one component: any decoration that should track the theme — a hairline, a wash, a shadow — is better mixed from a token than picked as a literal. A literal needs a dark-mode rule to correct it later; a mix never drifts from the token it was built from.
Why there is no noise variant
Dots, grid and glow are all gradients — background-image paints them in one
declaration each. Noise is not a gradient. CSS alone cannot draw it; the
only way to get grainy noise without JavaScript is an inline SVG
feTurbulence filter, applied as a data URI:
.sk-texture[data-texture="noise"] {
filter: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><filter id="n"><feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" stitchTiles="stitch"/></filter></svg>#n');
}
That would be the largest thing in the component by a wide margin, and it is
a different kind of primitive: a filter recomputes and composites the layer
rather than painting a value, which is a real cost on a position: fixed
layer covering the full viewport. This is a deliberate omission, not a gap —
copy the recipe above if you want it.
Keyboard contract
There is nothing to operate. .sk-texture is decoration: aria-hidden="true"
keeps it out of the accessibility tree, and pointer-events: none keeps it
out of the hit-testing order. No key does anything to it, and none should.
Accessibility notes
aria-hidden="true" and pointer-events: none are both required in the
markup and CSS respectively — either alone is not enough. aria-hidden
removes it from the accessibility tree; pointer-events: none keeps a
sighted pointer user from ever landing a click on it, since the layer sits
position: fixed above the document flow. The negative z-index keeps it
behind real content so it never obscures anything.
Degradation
color-mix() is Baseline widely available. There is no @supports block in
this component and nothing to degrade.
| Feature used | Baseline status | Behavior without it |
|---|---|---|
color-mix() | Widely available | N/A — no browser in the support policy lacks it |
Theming
| Property | Default | Controls |
|---|---|---|
--sk-texture-color | a color-mix() of --sk-color-text | Ink colour of the pattern |
--sk-texture-size | 1.75rem | Cell size for dots and grid; radius for glow |
--sk-texture-opacity | 1 | Overall strength, on top of the colour’s own alpha |