Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Backgrounds

Pro

Grain Background for React

Fine film grain over any surface, a speckle you feel more than see that re-rolls 24 times a second. For React with Base UI, when a flat colour or a gradient should look like film rather than a screen.

Framework

Grain lays fine film grain over any surface: a speckle you feel more than see, re-rolled 24 times a second like grain in projected film. The noise is two small SVG tiles of different sizes, drawn once by the browser; keyframes the install adds to your stylesheet jump them to new offsets in steps, so no JavaScript runs and nothing repaints while it moves. The tiles' noise is tuned so it never beats against the pixel grid, so no woven pattern or tile grid shows, at 1× or on a retina screen. It takes your theme's foreground colour, so it's dark print grain on a light page and pale film grain on a dark one.

Put it over a flat colour or a gradient to take the digital flatness off a hero. For a texture that holds still, use Paper; for print dots rather than film grain, Halftone. It pairs with Mesh Gradient underneath, or with Spotlight for a cinematic glow. For signal noise that tears and shifts, use Glitch; for a quieter, static texture, Dot Pattern.

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Usage

import { Grain } from "@/components/ui/grain";
<Grain className="min-h-[480px]">
  <h1>Every surface has a texture</h1>
</Grain>

Grain is a container: your content goes inside it, and the grain sits behind that content and above the container's own background. Give it a size, a background colour or a gradient with className; for a whole page, className="fixed inset-0 -z-10" with no children.

Demos

Grainy Gradient

Put the gradient on the root with className, and the grain lands on top of it. intensity="strong" and grainColor="white" give the gritty look that suits a dark, saturated gradient.

Preview width

Warm Film

grainColor takes any CSS colour: amber over dark brown reads as warm film stock. speed={0.5} halves the rate to 12 jumps a second, for a slower, drifting grain.

Preview width

Accessibility

The grain is decoration: it's hidden from assistive tech and never takes the pointer, so everything you put inside works as usual. With reduced motion the grain holds still, as a texture, and in forced-colors mode it's hidden. Its overall brightness stays the same from one step to the next, so it doesn't flash; speed is capped at twice the default. At the default intensity the specks cover a few percent of the surface at low opacity, so text in your foreground colour keeps its contrast; use intensity="subtle" behind small body text over a busy gradient.

FAQ

How do I make the grain stronger or fainter?

Use intensity: subtle behind body text, medium (the default, felt rather than seen) for a hero, strong for a gritty dark section or a saturated gradient.

How do I get the grainy-gradient look?

Put the gradient on the root, such as className="bg-[radial-gradient(…)]". The grain sits above the root's background and under your content.

Can I change the grain's colour?

Pass any CSS colour to grainColor, such as amber over dark brown for warm film or white over a deep gradient. By default it's your theme's foreground colour, so it follows light and dark mode.

Why doesn't a grid or a repeat show?

Two things. The noise is drawn at a frequency that doesn't beat against the pixel grid (near one speck per pixel, noise turns into a woven grid), and the two tiles have prime sizes, 241 and 179 pixels, so their sum never repeats on screen.

How fast does it move, and can I change that?

24 jumps a second by default, the rate of projected film. speed multiplies it, from 0.25 (6 a second, a slow drift) to 2 (48 a second).

Does it cost anything while it runs?

No script runs. Two layers jump between fixed offsets on the compositor, the part of the browser that moves layers without repainting them, and the noise tiles are drawn once. Offscreen, the browser skips it.

What happens with reduced motion?

The grain holds still, so the surface keeps its texture without moving. It never flashes either way: its brightness doesn't change from step to step.

API Reference

Grain

<div> data-slot="grain"

PropType
grainColor?string

The speckle's colour: any CSS colour.

Default
"var(--foreground)"
intensity?"subtle" | "medium" | "strong"

How strong the grain is: subtle behind body text, medium for a hero, strong for a gritty dark section.

Type
"subtle" | "medium" | "strong"
Default
"medium"
speed?number

How fast the grain re-rolls: a multiplier of 24 frames a second, from 0.25 to 2.

Default
1
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

CSS variableType
--grain-colorstring

The speckle's colour (var(--foreground)).

--grain-durationstring

One cycle of 12 steps (500ms): longer is slower.

All 100 backgrounds