08

Textures

Surface character that kills plastic flatness — grain, relief, fiber, brushed metal.

A flat fill reads as plastic. Textures are the family of surface effects that give the same hue a material: grain, relief, paper fiber, brushed specular. Normal maps are one 3D tool for lighting response — not the whole topic. For pure grain, see Noise.

Switch modes on the card below. Same base hue; only the surface changes.

Plastic is the wrong default. Grain breaks banding. Relief catches light. Paper fiber softens digital sheen. Brushed metal sells anisotropy with streaky specular — all without leaving CSS/SVG for most product chrome.

A family, not a filter

Pick the texture that matches the material you are implying:

  • Grain / noise — film, soft UI cards, banded gradients
  • Relief / bevel — inset panels, tactile chrome (pointer light optional)
  • Paper / fiber — stationery, quiet editorial surfaces
  • Brushed — metal, toolbars, hardware-adjacent UI

When the surface must light correctly under a moving 3D camera, leave CSS and use real normal maps in Three.js / Filament. Do not purple-sludge the specular. Keep highlights near white or a cool tint of the surface. Scarce accent (#c0300f) belongs on actions, not on every lit edge.

Usage

<!-- Grain overlay (SVG filter defined once on the page) -->
<div class="relative isolate overflow-hidden rounded-xl bg-[#3a4556]">
    <div
        aria-hidden
        class="pointer-events-none absolute inset-0 opacity-[0.14] mix-blend-overlay"
        style="filter: url(#grain)"
    ></div>
</div>

<!-- Relief: inset bevel + movable specular -->
<div
    class="h-32 rounded-xl"
    style="
        background:
            radial-gradient(circle at 62% 28%, rgba(255,255,255,0.55), transparent 42%),
            linear-gradient(145deg, #4a5568, #1e2633);
        box-shadow:
            inset 0 1px 0 rgba(255,255,255,0.35),
            inset 0 -1px 0 rgba(0,0,0,0.35);
    "
></div>
/* Grain: empty overlay + SVG feTurbulence */
.grain-overlay {
    position: absolute;
    inset: 0;
    opacity: 0.14;
    mix-blend-mode: overlay;
    filter: url(#grain);
    pointer-events: none;
}

/* Relief: bevel + specular that follows --lx / --ly */
.relief {
    background:
        radial-gradient(
            circle at var(--lx, 60%) var(--ly, 30%),
            rgba(255, 255, 255, 0.55),
            transparent 42%
        ),
        linear-gradient(145deg, #4a5568, #1e2633);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.35),
        inset 0 -1px 0 rgba(0, 0, 0, 0.35);
}

/* Paper fiber: fine horizontal lines */
.paper::after {
    content: "";
    position: absolute;
    inset: 0;
    opacity: 0.18;
    background: repeating-linear-gradient(
        0deg,
        transparent,
        transparent 1px,
        rgba(0, 0, 0, 0.08) 1px,
        rgba(0, 0, 0, 0.08) 2px
    );
}

Textures are a design choice, not a decoration pass. Match the surface to the object — then stop adding layers.