---
title: Textured Section Backgrounds (Pure CSS)
slug: textured-section-backgrounds
category: Media
summary: 18 modern texture and pattern recipes for section backgrounds — dots, grids, noise grain, mesh gradients, spotlight beams, animated auroras — all pure CSS or inline SVG, zero image files.
tags: [tailwind, css, backgrounds, ui-polish, design]
status: stable
visibility: public
source_project: llamapress.ai/texture-demo
layers: [view]
---

# Textured Section Backgrounds (Pure CSS)

> ⚠️ **Cookbook example — not live code.** (KEEP THIS CALLOUT.) Every code block below
> is an **example snippet**, **not part of the llamapress.ai codebase**, and **not
> running on this server**. This is a reference recipe for a **Leo instance (an AI coding
> agent) to implement in its own app** — read it to understand the pattern, then recreate
> it there.

Flat `bg-white` / `bg-slate-50` sections read as unfinished. A faint pattern, a glow, and
2–5% noise make the same layout feel designed. Every recipe here is **pure CSS or an
inline SVG data URI — no image files, no build step, no new dependencies**. They work in
any Rails view with Tailwind (Play CDN or compiled).

> **When to use:** landing/marketing pages, hero sections, CTA breaks, empty states,
> dashboards that need a branded feel.
> **When not to:** dense data screens (tables, forms) — texture behind small text hurts
> readability. And never as a substitute for good spacing/hierarchy; texture is the last
> 10%, not the first 90%.

---

## The 80/20 in one breath

1. Every textured section is the same skeleton: a `relative overflow-hidden` section, one
   or more `absolute inset-0` **pattern layer divs**, then a `relative` content wrapper
   on top.
2. Use **CSS gradients** for anything mathematical (dots, grids, stripes, rings) and a
   **tiny inline SVG `feTurbulence` data URI** for noise/grain. Real image files only for
   organic textures (paper fiber, watercolor).
3. Put the pattern CSS in **inline `style=""` attributes**, not Tailwind arbitrary-value
   classes — see Gotchas.
4. Add a **`mask-image` fade** (radial or linear) so the pattern dissolves instead of
   ending abruptly — the mask does most of the aesthetic work.
5. Keep opacity LOW. If a viewer consciously notices the texture, it's too strong.
6. The strongest default modern look: **base color + radial glow + fading dots/grid +
   3–5% noise** (recipe 5 below).

---

## Layer 1 — The skeleton (used by every recipe)

```erb
<%# app/views/pages/home.html.erb (or any view/partial) — the shared wrapper shape %>
<section class="relative overflow-hidden bg-white">
  <!-- pattern layer(s): absolute, behind content, never interactive -->
  <div class="pointer-events-none absolute inset-0" style="/* pattern CSS here */"></div>

  <!-- content: relative so it stacks above the pattern -->
  <div class="relative max-w-6xl mx-auto px-6 py-24">
    ...
  </div>
</section>
```

The noise texture used by several recipes is this inline SVG data URI (define it once —
an ERB local, a helper, or just paste the string):

```erb
<%# Tiny feTurbulence noise tile as a data URI — no asset file needed. %>
<% noise_svg = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E" %>
```

## Layer 2 — Light-background patterns

Each block below is the **pattern layer div only** — drop it into the skeleton above.

**1. Sparse dots** — friendly, neutral sections:

```html
<!-- pattern layer: sparse dots -->
<div class="pointer-events-none absolute inset-0 opacity-40"
     style="background-image: radial-gradient(circle, #94a3b8 1px, transparent 1px); background-size: 22px 22px;"></div>
```

**2. Subtle grid** — technical/product sections (two crossed 1px gradients, ~6% black):

```html
<!-- pattern layer: subtle grid -->
<div class="pointer-events-none absolute inset-0"
     style="background-image: linear-gradient(to right, rgba(15,23,42,0.06) 1px, transparent 1px), linear-gradient(to bottom, rgba(15,23,42,0.06) 1px, transparent 1px); background-size: 32px 32px;"></div>
```

**3. Fading grid (radial mask)** — the modern upgrade of #2; the fade IS the design:

```html
<!-- pattern layer: grid that dissolves toward the edges -->
<div class="pointer-events-none absolute inset-0"
     style="background-image: linear-gradient(to right, rgba(15,23,42,0.09) 1px, transparent 1px), linear-gradient(to bottom, rgba(15,23,42,0.09) 1px, transparent 1px); background-size: 32px 32px; -webkit-mask-image: radial-gradient(ellipse at center, black, transparent 72%); mask-image: radial-gradient(ellipse at center, black, transparent 72%);"></div>
```

**4. Fine diagonal stripes** — callouts/comparison blocks; tint to the surface color:

```html
<!-- pattern layer: fine 135° stripes (amber-tinted example) -->
<div class="pointer-events-none absolute inset-0 opacity-60"
     style="background-image: repeating-linear-gradient(135deg, rgba(120,53,15,0.06) 0, rgba(120,53,15,0.06) 1px, transparent 1px, transparent 10px);"></div>
```

**11. Plus / cross grid** — the Vercel/Linear look; tiny "+" marks from an inline SVG:

```html
<!-- pattern layer: "+" tile, masked to fade at edges. The data URI draws one plus. -->
<div class="pointer-events-none absolute inset-0 opacity-50"
     style="background-image: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28'%3E%3Cpath d='M14 10v8M10 14h8' stroke='%2394a3b8' stroke-width='1.2' stroke-linecap='round'/%3E%3C/svg%3E&quot;); background-size: 28px 28px; -webkit-mask-image: radial-gradient(ellipse at center, black, transparent 75%); mask-image: radial-gradient(ellipse at center, black, transparent 75%);"></div>
```

**12. Graph paper** — major + minor gridlines in ONE element (4 layers, 2 sizes):

```html
<!-- pattern layer: 16px minor grid + 80px major grid -->
<div class="pointer-events-none absolute inset-0"
     style="background-image: linear-gradient(to right, rgba(15,23,42,0.09) 1px, transparent 1px), linear-gradient(to bottom, rgba(15,23,42,0.09) 1px, transparent 1px), linear-gradient(to right, rgba(15,23,42,0.04) 1px, transparent 1px), linear-gradient(to bottom, rgba(15,23,42,0.04) 1px, transparent 1px); background-size: 80px 80px, 80px 80px, 16px 16px, 16px 16px;"></div>
```

**13. Halftone fade** — dots that shrink as they descend (3 sizes, staggered masks):

```html
<!-- pattern layers: 2.4px → 1.4px → 0.8px dots, each masked to its own vertical band -->
<div class="pointer-events-none absolute inset-0 opacity-25"
     style="background-image: radial-gradient(circle, #475569 2.4px, transparent 2.4px); background-size: 16px 16px; -webkit-mask-image: linear-gradient(to bottom, black, transparent 45%); mask-image: linear-gradient(to bottom, black, transparent 45%);"></div>
<div class="pointer-events-none absolute inset-0 opacity-25"
     style="background-image: radial-gradient(circle, #475569 1.4px, transparent 1.4px); background-size: 16px 16px; -webkit-mask-image: linear-gradient(to bottom, transparent 15%, black 45%, transparent 75%); mask-image: linear-gradient(to bottom, transparent 15%, black 45%, transparent 75%);"></div>
<div class="pointer-events-none absolute inset-0 opacity-25"
     style="background-image: radial-gradient(circle, #475569 0.8px, transparent 0.8px); background-size: 16px 16px; -webkit-mask-image: linear-gradient(to bottom, transparent 55%, black 85%, transparent); mask-image: linear-gradient(to bottom, transparent 55%, black 85%, transparent);"></div>
```

**7. Contour rings** — faux-topographic, premium editorial; anchor off-center:

```html
<!-- pattern layer: repeating concentric rings -->
<div class="pointer-events-none absolute inset-0 opacity-50"
     style="background-image: repeating-radial-gradient(circle at 80% 20%, rgba(6,95,70,0.08) 0, rgba(6,95,70,0.08) 1px, transparent 1px, transparent 48px);"></div>
```

**10. Checkerboard fading upward** — CTA/footer transitions:

```html
<!-- pattern layer: conic checkerboard, visible only near the bottom edge -->
<div class="pointer-events-none absolute inset-0 opacity-35"
     style="background-image: conic-gradient(rgba(15,23,42,0.06) 90deg, transparent 90deg 180deg, rgba(15,23,42,0.06) 180deg 270deg, transparent 270deg); background-size: 48px 48px; -webkit-mask-image: linear-gradient(to top, black, transparent 85%); mask-image: linear-gradient(to top, black, transparent 85%);"></div>
```

## Layer 3 — Dark heroes

**5. Glow + fading dots + noise — the strongest default modern look.** Three stacked
layers on `bg-slate-950`:

```html
<!-- on <section class="relative overflow-hidden bg-slate-950 text-white"> -->
<!-- layer 1: radial glow at top -->
<div class="pointer-events-none absolute inset-0"
     style="background-image: radial-gradient(circle at 50% 0%, rgba(99,102,241,0.25), transparent 45%);"></div>
<!-- layer 2: dots fading downward -->
<div class="pointer-events-none absolute inset-0 opacity-20"
     style="background-image: radial-gradient(circle, rgba(255,255,255,0.3) 1px, transparent 1px); background-size: 24px 24px; -webkit-mask-image: linear-gradient(to bottom, black, transparent 80%); mask-image: linear-gradient(to bottom, black, transparent 80%);"></div>
<!-- layer 3: 5% noise grain (noise_svg from Layer 1 above) -->
<div class="pointer-events-none absolute inset-0 mix-blend-soft-light"
     style="background-image: url(&quot;<%= noise_svg %>&quot;); opacity: 0.05;"></div>
```

**14. Spotlight beams** — conic-gradient light shafts anchored above the viewport:

```html
<!-- pattern layer: three beams from above, fading down; add the noise layer on top -->
<div class="pointer-events-none absolute inset-0"
     style="background-image: conic-gradient(from 165deg at 30% -10%, transparent 0deg, rgba(99,102,241,0.18) 12deg, transparent 30deg), conic-gradient(from 195deg at 70% -10%, transparent 0deg, rgba(56,189,248,0.14) 14deg, transparent 32deg), conic-gradient(from 178deg at 50% -15%, transparent 0deg, rgba(255,255,255,0.06) 8deg, transparent 20deg); -webkit-mask-image: linear-gradient(to bottom, black 40%, transparent 95%); mask-image: linear-gradient(to bottom, black 40%, transparent 95%);"></div>
```

**18. Perspective horizon grid** — a grid tilted into a 3D floor. Strong flavor; one per
site, if at all:

```html
<!-- bottom two-thirds of a dark section: grid rotated away from the viewer -->
<div class="absolute inset-x-0 bottom-0 h-2/3" style="perspective: 400px;">
  <div class="pointer-events-none absolute inset-0"
       style="background-image: linear-gradient(to right, rgba(129,140,248,0.25) 1px, transparent 1px), linear-gradient(to bottom, rgba(129,140,248,0.25) 1px, transparent 1px); background-size: 48px 48px; transform: rotateX(62deg); transform-origin: top; -webkit-mask-image: linear-gradient(to bottom, transparent, black 35%); mask-image: linear-gradient(to bottom, transparent, black 35%);"></div>
</div>
<div class="pointer-events-none absolute inset-0"
     style="background-image: radial-gradient(circle at 50% 100%, rgba(99,102,241,0.25), transparent 55%);"></div>
```

## Layer 4 — Color washes & grain

**6. Grain over a gradient** — noise is the highest-leverage texture; ~8–12% on gradients:

```html
<section class="relative overflow-hidden text-white"
         style="background-image: linear-gradient(135deg, #1d4ed8 0%, #7c3aed 100%);">
  <div class="pointer-events-none absolute inset-0 mix-blend-soft-light"
       style="background-image: url(&quot;<%= noise_svg %>&quot;); opacity: 0.12;"></div>
  <div class="relative max-w-6xl mx-auto px-6 py-20">...</div>
</section>
```

**15. Mesh / aurora gradient** — the Stripe-style wash; four radial-gradients at
different corners, finished with grain:

```html
<section class="relative overflow-hidden"
         style="background-color: #fafaf9; background-image: radial-gradient(at 15% 25%, rgba(125,211,252,0.5) 0px, transparent 50%), radial-gradient(at 85% 15%, rgba(216,180,254,0.45) 0px, transparent 50%), radial-gradient(at 70% 85%, rgba(253,186,116,0.35) 0px, transparent 50%), radial-gradient(at 25% 90%, rgba(167,243,208,0.4) 0px, transparent 50%);">
  <div class="pointer-events-none absolute inset-0 mix-blend-soft-light"
       style="background-image: url(&quot;<%= noise_svg %>&quot;); opacity: 0.06;"></div>
  <div class="relative max-w-6xl mx-auto px-6 py-24">...</div>
</section>
```

**8. Soft blobs** — consumer/friendly surfaces; blurred circles at ~30% opacity:

```html
<!-- pattern layers: three heavily blurred color fields -->
<div class="pointer-events-none absolute -top-24 -left-24 w-96 h-96 rounded-full bg-pink-300 opacity-30" style="filter: blur(90px);"></div>
<div class="pointer-events-none absolute -bottom-32 right-0 w-96 h-96 rounded-full bg-sky-300 opacity-30" style="filter: blur(100px);"></div>
<div class="pointer-events-none absolute top-1/3 left-1/2 w-72 h-72 rounded-full bg-amber-200 opacity-30" style="filter: blur(80px);"></div>
```

**16. Animated aurora + glass panel** — blobs drifting on slow transform loops, with a
frosted card giving text a clean landing zone on top:

```html
<!-- in a <style> tag or your CSS: transform-only keyframes (GPU-cheap) -->
<style>
  @keyframes aurora-a { 0%,100% { transform: translate(0,0) scale(1); } 33% { transform: translate(50px,-35px) scale(1.15); } 66% { transform: translate(-35px,25px) scale(0.95); } }
  @keyframes aurora-b { 0%,100% { transform: translate(0,0) scale(1); } 40% { transform: translate(-45px,30px) scale(1.1); } 70% { transform: translate(30px,-20px) scale(0.9); } }
</style>

<section class="relative overflow-hidden bg-indigo-50">
  <div class="pointer-events-none absolute -top-20 -left-20 w-96 h-96 rounded-full bg-violet-300 opacity-40"
       style="filter: blur(80px); animation: aurora-a 18s ease-in-out infinite;"></div>
  <div class="pointer-events-none absolute top-10 right-0 w-96 h-96 rounded-full bg-sky-300 opacity-40"
       style="filter: blur(90px); animation: aurora-b 24s ease-in-out infinite;"></div>

  <div class="relative max-w-6xl mx-auto px-6 py-24">
    <!-- the glass panel: how content sits on ANY texture -->
    <div class="max-w-xl rounded-3xl border border-white/60 bg-white/60 backdrop-blur-xl shadow-lg p-8">
      ...
    </div>
  </div>
</section>
```

## Layer 5 — Editorial / print textures

**9. Paper grain on light** — same noise SVG at 5% on `multiply` over warm stone
(`bg-stone-100`); almost invisible on purpose:

```html
<div class="pointer-events-none absolute inset-0 mix-blend-multiply"
     style="background-image: url(&quot;<%= noise_svg %>&quot;); opacity: 0.05;"></div>
```

**17. Ruled editorial lines** — notebook rules + a red margin line; pair with a serif
headline. Good for about pages and founder letters:

```html
<section class="relative overflow-hidden" style="background-color: #fdfcf8;">
  <div class="pointer-events-none absolute inset-0"
       style="background-image: repeating-linear-gradient(to bottom, transparent 0, transparent 31px, rgba(120,113,108,0.14) 31px, rgba(120,113,108,0.14) 32px);"></div>
  <div class="pointer-events-none absolute inset-y-0 hidden md:block" style="left: 88px; width: 1px; background: rgba(244,63,94,0.25);"></div>
  <div class="relative max-w-6xl mx-auto px-6 md:pl-32 py-20">...</div>
</section>
```

---

## Gotchas (the hard-won stuff)

- **Use inline `style=""` for the pattern CSS, not Tailwind arbitrary-value classes.**
  Arbitrary values like `[background-image:radial-gradient(...)]` only work with the
  Play CDN or a JIT build that saw the class. Inline styles work everywhere — including
  boxes with compiled Tailwind — and survive copy-paste between apps. Use Tailwind
  classes only for layout/positioning (`absolute inset-0 opacity-40`).
- **Always ship `mask-image` with the `-webkit-mask-image` twin.** Chrome/Edge/older
  Safari need the prefix; without it the fade silently disappears and the pattern ends
  in a hard edge.
- **`pointer-events-none` on every pattern layer.** An `absolute inset-0` div without it
  sits on top of your content in hit-testing and eats every click. This is the #1 way a
  texture "breaks the page."
- **The skeleton ordering matters:** section is `relative overflow-hidden`, pattern
  layers are `absolute`, content wrapper is `relative`. Forget `relative` on the content
  and it z-fights with (or hides under) the pattern. Forget `overflow-hidden` and
  off-canvas blobs create horizontal scrollbars.
- **Quoting the data URI inside an inline style in ERB/HTML:** the style attribute is
  delimited with double quotes, so wrap the `url(...)` value in `&quot;...&quot;` (or
  single-quote the attribute). The SVG itself must be URL-encoded (`%3C` for `<`, `%23`
  for `#`, `%25` for `%`).
- **Opacity is the whole game.** Noise: 2–5% on dark, 3–8% on gradients, 1–3% on white.
  Line/dot patterns: keep line color under ~10% opacity black. If you notice the
  texture, halve the opacity.
- **Don't texture every section.** A typical page: hero gets the full 3-layer treatment,
  one mid-page break gets dots or a grid, the CTA gets grain + gradient. Everything else
  stays plain — that contrast is what makes the textured sections land.
- **Dense patterns cause moiré** (shimmer while scrolling). Keep tiles ≥ 16px, dots
  ≥ 1px, and test on a low-DPI screen if you go finer.
- **Animate `transform` only** (like the aurora keyframes). Animating `background-position`
  or `filter` repaints every frame and melts low-end machines. 18–30s loops read as
  "alive," anything faster reads as "loading spinner."
- **Text needs a landing zone.** Never put small text straight on a busy pattern — use a
  mask to clear the center, or a `backdrop-blur` glass panel (recipe 16).
- **`mix-blend-soft-light` needs a stacking context that includes the background.** If
  the noise looks like a flat gray sheet, the blend is compositing against the wrong
  layer — make sure the noise div is a sibling *inside* the section that owns the
  background color.

---

## Files this pattern touches

```
app/views/<any page>.html.erb          # recipes drop into existing views
app/views/shared/_textured_section.html.erb   # optional: extract the skeleton as a partial
```

No migrations, no routes, no JavaScript dependencies, no image assets.

## How to adapt to your schema

1. Pick 3–4 recipes max for a site (e.g. dark hero #5, plus grid #11, mesh #15, grain
   #6) — a restrained set reads as a design system; using all 18 reads as a demo page.
2. Swap the colors: replace the indigo/sky RGBA values with your brand palette. Keep the
   alpha values — they're tuned; change only the RGB.
3. For a reusable system, extract the skeleton into a shared partial that takes the
   pattern layer as a block or a `pattern:` key, so every page speaks the same visual
   language.
4. Light vs dark: each recipe states its intended surface. To flip one, invert the
   pattern color (dark lines on light ↔ `rgba(255,255,255,…)` on dark) and re-tune
   opacity down.
5. Safe to drop: the noise layer (pure garnish), the animation (static blobs still look
   good), and the `-webkit-` prefixes only if you know your users are on current Safari.
