{"slug":"textured-section-backgrounds","meta":{"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"]},"body":"# Textured Section Backgrounds (Pure CSS)\n\n\u003e ⚠️ **Cookbook example — not live code.** (KEEP THIS CALLOUT.) Every code block below\n\u003e is an **example snippet**, **not part of the llamapress.ai codebase**, and **not\n\u003e running on this server**. This is a reference recipe for a **Leo instance (an AI coding\n\u003e agent) to implement in its own app** — read it to understand the pattern, then recreate\n\u003e it there.\n\nFlat `bg-white` / `bg-slate-50` sections read as unfinished. A faint pattern, a glow, and\n2–5% noise make the same layout feel designed. Every recipe here is **pure CSS or an\ninline SVG data URI — no image files, no build step, no new dependencies**. They work in\nany Rails view with Tailwind (Play CDN or compiled).\n\n\u003e **When to use:** landing/marketing pages, hero sections, CTA breaks, empty states,\n\u003e dashboards that need a branded feel.\n\u003e **When not to:** dense data screens (tables, forms) — texture behind small text hurts\n\u003e readability. And never as a substitute for good spacing/hierarchy; texture is the last\n\u003e 10%, not the first 90%.\n\n---\n\n## The 80/20 in one breath\n\n1. Every textured section is the same skeleton: a `relative overflow-hidden` section, one\n   or more `absolute inset-0` **pattern layer divs**, then a `relative` content wrapper\n   on top.\n2. Use **CSS gradients** for anything mathematical (dots, grids, stripes, rings) and a\n   **tiny inline SVG `feTurbulence` data URI** for noise/grain. Real image files only for\n   organic textures (paper fiber, watercolor).\n3. Put the pattern CSS in **inline `style=\"\"` attributes**, not Tailwind arbitrary-value\n   classes — see Gotchas.\n4. Add a **`mask-image` fade** (radial or linear) so the pattern dissolves instead of\n   ending abruptly — the mask does most of the aesthetic work.\n5. Keep opacity LOW. If a viewer consciously notices the texture, it's too strong.\n6. The strongest default modern look: **base color + radial glow + fading dots/grid +\n   3–5% noise** (recipe 5 below).\n\n---\n\n## Layer 1 — The skeleton (used by every recipe)\n\n```erb\n\u003c%# app/views/pages/home.html.erb (or any view/partial) — the shared wrapper shape %\u003e\n\u003csection class=\"relative overflow-hidden bg-white\"\u003e\n  \u003c!-- pattern layer(s): absolute, behind content, never interactive --\u003e\n  \u003cdiv class=\"pointer-events-none absolute inset-0\" style=\"/* pattern CSS here */\"\u003e\u003c/div\u003e\n\n  \u003c!-- content: relative so it stacks above the pattern --\u003e\n  \u003cdiv class=\"relative max-w-6xl mx-auto px-6 py-24\"\u003e\n    ...\n  \u003c/div\u003e\n\u003c/section\u003e\n```\n\nThe noise texture used by several recipes is this inline SVG data URI (define it once —\nan ERB local, a helper, or just paste the string):\n\n```erb\n\u003c%# Tiny feTurbulence noise tile as a data URI — no asset file needed. %\u003e\n\u003c% 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\" %\u003e\n```\n\n## Layer 2 — Light-background patterns\n\nEach block below is the **pattern layer div only** — drop it into the skeleton above.\n\n**1. Sparse dots** — friendly, neutral sections:\n\n```html\n\u003c!-- pattern layer: sparse dots --\u003e\n\u003cdiv class=\"pointer-events-none absolute inset-0 opacity-40\"\n     style=\"background-image: radial-gradient(circle, #94a3b8 1px, transparent 1px); background-size: 22px 22px;\"\u003e\u003c/div\u003e\n```\n\n**2. Subtle grid** — technical/product sections (two crossed 1px gradients, ~6% black):\n\n```html\n\u003c!-- pattern layer: subtle grid --\u003e\n\u003cdiv class=\"pointer-events-none absolute inset-0\"\n     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;\"\u003e\u003c/div\u003e\n```\n\n**3. Fading grid (radial mask)** — the modern upgrade of #2; the fade IS the design:\n\n```html\n\u003c!-- pattern layer: grid that dissolves toward the edges --\u003e\n\u003cdiv class=\"pointer-events-none absolute inset-0\"\n     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%);\"\u003e\u003c/div\u003e\n```\n\n**4. Fine diagonal stripes** — callouts/comparison blocks; tint to the surface color:\n\n```html\n\u003c!-- pattern layer: fine 135° stripes (amber-tinted example) --\u003e\n\u003cdiv class=\"pointer-events-none absolute inset-0 opacity-60\"\n     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);\"\u003e\u003c/div\u003e\n```\n\n**11. Plus / cross grid** — the Vercel/Linear look; tiny \"+\" marks from an inline SVG:\n\n```html\n\u003c!-- pattern layer: \"+\" tile, masked to fade at edges. The data URI draws one plus. --\u003e\n\u003cdiv class=\"pointer-events-none absolute inset-0 opacity-50\"\n     style=\"background-image: url(\u0026quot;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\u0026quot;); 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%);\"\u003e\u003c/div\u003e\n```\n\n**12. Graph paper** — major + minor gridlines in ONE element (4 layers, 2 sizes):\n\n```html\n\u003c!-- pattern layer: 16px minor grid + 80px major grid --\u003e\n\u003cdiv class=\"pointer-events-none absolute inset-0\"\n     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;\"\u003e\u003c/div\u003e\n```\n\n**13. Halftone fade** — dots that shrink as they descend (3 sizes, staggered masks):\n\n```html\n\u003c!-- pattern layers: 2.4px → 1.4px → 0.8px dots, each masked to its own vertical band --\u003e\n\u003cdiv class=\"pointer-events-none absolute inset-0 opacity-25\"\n     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%);\"\u003e\u003c/div\u003e\n\u003cdiv class=\"pointer-events-none absolute inset-0 opacity-25\"\n     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%);\"\u003e\u003c/div\u003e\n\u003cdiv class=\"pointer-events-none absolute inset-0 opacity-25\"\n     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);\"\u003e\u003c/div\u003e\n```\n\n**7. Contour rings** — faux-topographic, premium editorial; anchor off-center:\n\n```html\n\u003c!-- pattern layer: repeating concentric rings --\u003e\n\u003cdiv class=\"pointer-events-none absolute inset-0 opacity-50\"\n     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);\"\u003e\u003c/div\u003e\n```\n\n**10. Checkerboard fading upward** — CTA/footer transitions:\n\n```html\n\u003c!-- pattern layer: conic checkerboard, visible only near the bottom edge --\u003e\n\u003cdiv class=\"pointer-events-none absolute inset-0 opacity-35\"\n     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%);\"\u003e\u003c/div\u003e\n```\n\n## Layer 3 — Dark heroes\n\n**5. Glow + fading dots + noise — the strongest default modern look.** Three stacked\nlayers on `bg-slate-950`:\n\n```html\n\u003c!-- on \u003csection class=\"relative overflow-hidden bg-slate-950 text-white\"\u003e --\u003e\n\u003c!-- layer 1: radial glow at top --\u003e\n\u003cdiv class=\"pointer-events-none absolute inset-0\"\n     style=\"background-image: radial-gradient(circle at 50% 0%, rgba(99,102,241,0.25), transparent 45%);\"\u003e\u003c/div\u003e\n\u003c!-- layer 2: dots fading downward --\u003e\n\u003cdiv class=\"pointer-events-none absolute inset-0 opacity-20\"\n     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%);\"\u003e\u003c/div\u003e\n\u003c!-- layer 3: 5% noise grain (noise_svg from Layer 1 above) --\u003e\n\u003cdiv class=\"pointer-events-none absolute inset-0 mix-blend-soft-light\"\n     style=\"background-image: url(\u0026quot;\u003c%= noise_svg %\u003e\u0026quot;); opacity: 0.05;\"\u003e\u003c/div\u003e\n```\n\n**14. Spotlight beams** — conic-gradient light shafts anchored above the viewport:\n\n```html\n\u003c!-- pattern layer: three beams from above, fading down; add the noise layer on top --\u003e\n\u003cdiv class=\"pointer-events-none absolute inset-0\"\n     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%);\"\u003e\u003c/div\u003e\n```\n\n**18. Perspective horizon grid** — a grid tilted into a 3D floor. Strong flavor; one per\nsite, if at all:\n\n```html\n\u003c!-- bottom two-thirds of a dark section: grid rotated away from the viewer --\u003e\n\u003cdiv class=\"absolute inset-x-0 bottom-0 h-2/3\" style=\"perspective: 400px;\"\u003e\n  \u003cdiv class=\"pointer-events-none absolute inset-0\"\n       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%);\"\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cdiv class=\"pointer-events-none absolute inset-0\"\n     style=\"background-image: radial-gradient(circle at 50% 100%, rgba(99,102,241,0.25), transparent 55%);\"\u003e\u003c/div\u003e\n```\n\n## Layer 4 — Color washes \u0026 grain\n\n**6. Grain over a gradient** — noise is the highest-leverage texture; ~8–12% on gradients:\n\n```html\n\u003csection class=\"relative overflow-hidden text-white\"\n         style=\"background-image: linear-gradient(135deg, #1d4ed8 0%, #7c3aed 100%);\"\u003e\n  \u003cdiv class=\"pointer-events-none absolute inset-0 mix-blend-soft-light\"\n       style=\"background-image: url(\u0026quot;\u003c%= noise_svg %\u003e\u0026quot;); opacity: 0.12;\"\u003e\u003c/div\u003e\n  \u003cdiv class=\"relative max-w-6xl mx-auto px-6 py-20\"\u003e...\u003c/div\u003e\n\u003c/section\u003e\n```\n\n**15. Mesh / aurora gradient** — the Stripe-style wash; four radial-gradients at\ndifferent corners, finished with grain:\n\n```html\n\u003csection class=\"relative overflow-hidden\"\n         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%);\"\u003e\n  \u003cdiv class=\"pointer-events-none absolute inset-0 mix-blend-soft-light\"\n       style=\"background-image: url(\u0026quot;\u003c%= noise_svg %\u003e\u0026quot;); opacity: 0.06;\"\u003e\u003c/div\u003e\n  \u003cdiv class=\"relative max-w-6xl mx-auto px-6 py-24\"\u003e...\u003c/div\u003e\n\u003c/section\u003e\n```\n\n**8. Soft blobs** — consumer/friendly surfaces; blurred circles at ~30% opacity:\n\n```html\n\u003c!-- pattern layers: three heavily blurred color fields --\u003e\n\u003cdiv class=\"pointer-events-none absolute -top-24 -left-24 w-96 h-96 rounded-full bg-pink-300 opacity-30\" style=\"filter: blur(90px);\"\u003e\u003c/div\u003e\n\u003cdiv class=\"pointer-events-none absolute -bottom-32 right-0 w-96 h-96 rounded-full bg-sky-300 opacity-30\" style=\"filter: blur(100px);\"\u003e\u003c/div\u003e\n\u003cdiv 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);\"\u003e\u003c/div\u003e\n```\n\n**16. Animated aurora + glass panel** — blobs drifting on slow transform loops, with a\nfrosted card giving text a clean landing zone on top:\n\n```html\n\u003c!-- in a \u003cstyle\u003e tag or your CSS: transform-only keyframes (GPU-cheap) --\u003e\n\u003cstyle\u003e\n  @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); } }\n  @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); } }\n\u003c/style\u003e\n\n\u003csection class=\"relative overflow-hidden bg-indigo-50\"\u003e\n  \u003cdiv class=\"pointer-events-none absolute -top-20 -left-20 w-96 h-96 rounded-full bg-violet-300 opacity-40\"\n       style=\"filter: blur(80px); animation: aurora-a 18s ease-in-out infinite;\"\u003e\u003c/div\u003e\n  \u003cdiv class=\"pointer-events-none absolute top-10 right-0 w-96 h-96 rounded-full bg-sky-300 opacity-40\"\n       style=\"filter: blur(90px); animation: aurora-b 24s ease-in-out infinite;\"\u003e\u003c/div\u003e\n\n  \u003cdiv class=\"relative max-w-6xl mx-auto px-6 py-24\"\u003e\n    \u003c!-- the glass panel: how content sits on ANY texture --\u003e\n    \u003cdiv class=\"max-w-xl rounded-3xl border border-white/60 bg-white/60 backdrop-blur-xl shadow-lg p-8\"\u003e\n      ...\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/section\u003e\n```\n\n## Layer 5 — Editorial / print textures\n\n**9. Paper grain on light** — same noise SVG at 5% on `multiply` over warm stone\n(`bg-stone-100`); almost invisible on purpose:\n\n```html\n\u003cdiv class=\"pointer-events-none absolute inset-0 mix-blend-multiply\"\n     style=\"background-image: url(\u0026quot;\u003c%= noise_svg %\u003e\u0026quot;); opacity: 0.05;\"\u003e\u003c/div\u003e\n```\n\n**17. Ruled editorial lines** — notebook rules + a red margin line; pair with a serif\nheadline. Good for about pages and founder letters:\n\n```html\n\u003csection class=\"relative overflow-hidden\" style=\"background-color: #fdfcf8;\"\u003e\n  \u003cdiv class=\"pointer-events-none absolute inset-0\"\n       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);\"\u003e\u003c/div\u003e\n  \u003cdiv class=\"pointer-events-none absolute inset-y-0 hidden md:block\" style=\"left: 88px; width: 1px; background: rgba(244,63,94,0.25);\"\u003e\u003c/div\u003e\n  \u003cdiv class=\"relative max-w-6xl mx-auto px-6 md:pl-32 py-20\"\u003e...\u003c/div\u003e\n\u003c/section\u003e\n```\n\n---\n\n## Gotchas (the hard-won stuff)\n\n- **Use inline `style=\"\"` for the pattern CSS, not Tailwind arbitrary-value classes.**\n  Arbitrary values like `[background-image:radial-gradient(...)]` only work with the\n  Play CDN or a JIT build that saw the class. Inline styles work everywhere — including\n  boxes with compiled Tailwind — and survive copy-paste between apps. Use Tailwind\n  classes only for layout/positioning (`absolute inset-0 opacity-40`).\n- **Always ship `mask-image` with the `-webkit-mask-image` twin.** Chrome/Edge/older\n  Safari need the prefix; without it the fade silently disappears and the pattern ends\n  in a hard edge.\n- **`pointer-events-none` on every pattern layer.** An `absolute inset-0` div without it\n  sits on top of your content in hit-testing and eats every click. This is the #1 way a\n  texture \"breaks the page.\"\n- **The skeleton ordering matters:** section is `relative overflow-hidden`, pattern\n  layers are `absolute`, content wrapper is `relative`. Forget `relative` on the content\n  and it z-fights with (or hides under) the pattern. Forget `overflow-hidden` and\n  off-canvas blobs create horizontal scrollbars.\n- **Quoting the data URI inside an inline style in ERB/HTML:** the style attribute is\n  delimited with double quotes, so wrap the `url(...)` value in `\u0026quot;...\u0026quot;` (or\n  single-quote the attribute). The SVG itself must be URL-encoded (`%3C` for `\u003c`, `%23`\n  for `#`, `%25` for `%`).\n- **Opacity is the whole game.** Noise: 2–5% on dark, 3–8% on gradients, 1–3% on white.\n  Line/dot patterns: keep line color under ~10% opacity black. If you notice the\n  texture, halve the opacity.\n- **Don't texture every section.** A typical page: hero gets the full 3-layer treatment,\n  one mid-page break gets dots or a grid, the CTA gets grain + gradient. Everything else\n  stays plain — that contrast is what makes the textured sections land.\n- **Dense patterns cause moiré** (shimmer while scrolling). Keep tiles ≥ 16px, dots\n  ≥ 1px, and test on a low-DPI screen if you go finer.\n- **Animate `transform` only** (like the aurora keyframes). Animating `background-position`\n  or `filter` repaints every frame and melts low-end machines. 18–30s loops read as\n  \"alive,\" anything faster reads as \"loading spinner.\"\n- **Text needs a landing zone.** Never put small text straight on a busy pattern — use a\n  mask to clear the center, or a `backdrop-blur` glass panel (recipe 16).\n- **`mix-blend-soft-light` needs a stacking context that includes the background.** If\n  the noise looks like a flat gray sheet, the blend is compositing against the wrong\n  layer — make sure the noise div is a sibling *inside* the section that owns the\n  background color.\n\n---\n\n## Files this pattern touches\n\n```\napp/views/\u003cany page\u003e.html.erb          # recipes drop into existing views\napp/views/shared/_textured_section.html.erb   # optional: extract the skeleton as a partial\n```\n\nNo migrations, no routes, no JavaScript dependencies, no image assets.\n\n## How to adapt to your schema\n\n1. Pick 3–4 recipes max for a site (e.g. dark hero #5, plus grid #11, mesh #15, grain\n   #6) — a restrained set reads as a design system; using all 18 reads as a demo page.\n2. Swap the colors: replace the indigo/sky RGBA values with your brand palette. Keep the\n   alpha values — they're tuned; change only the RGB.\n3. For a reusable system, extract the skeleton into a shared partial that takes the\n   pattern layer as a block or a `pattern:` key, so every page speaks the same visual\n   language.\n4. Light vs dark: each recipe states its intended surface. To flip one, invert the\n   pattern color (dark lines on light ↔ `rgba(255,255,255,…)` on dark) and re-tune\n   opacity down.\n5. Safe to drop: the noise layer (pure garnish), the animation (static blobs still look\n   good), and the `-webkit-` prefixes only if you know your users are on current Safari.\n"}