Textured Section Backgrounds (Pure CSS) | LlamaPress Cookbook
LlamaPress
Cookbook
Media Stable

Textured Section Backgrounds (Pure CSS)

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.

Proven on llamapress.ai/texture-demo view
Live demo — try it Open full screen

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)

<%# 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):

<%# 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:

<!-- 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):

<!-- 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:

<!-- 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:

<!-- 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:

<!-- 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):

<!-- 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):

<!-- 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:

<!-- 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:

<!-- 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:

<!-- 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:

<!-- 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:

<!-- 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:

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

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

<!-- 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:

<!-- 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:

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

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