Skip to content

Components / Backgrounds

Pattern

A repeating pattern behind a box's content: stripes, diagonals, checks, crosses, zigzags, waves or hexagons, drawn by CSS gradients or one small inline SVG tile used as a mask, in one colour at a strength, and faded towards the bottom or the edges if asked. It fills the positioned box it is placed in, behind the content, and never takes a click. DotPattern and GridPattern draw dots and a grid; this draws the rest. Zero JavaScript.

Live demo

diagonal
checks
crosses
zigzag
waves
hexagons

Live, from the library itself: scroll, hover, navigate away and back. The demo is never gated.

Measured

JavaScript of its own

0 B

CSS 6.2 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.

Browser support

Baseline · widely available

Chrome 120 · Firefox 53 · Safari 15.4; needs mask-image. Elsewhere: without masks the zigzags, waves and hexagons are a flat tint and no pattern fades.

Install

One command adds the integration, the base tokens and every component. Then import what you use.

npx astro add moonarc
pnpm astro add moonarc
bunx astro add moonarc
src/pages/index.astro
---
import Pattern from '@moonarc/core/Pattern';
---
<section style="position: relative">
  <Pattern kind="diagonal" size="18px" strength={0.1} fade="bottom" />
  <h2>Pricing</h2>
</section>
Copy it into your project instead (shadcn registry)

Owns the file, no dependency. The registry item also installs the base tokens.

terminal
npx shadcn@latest add https://moonarc.dev/r/pattern.json

The CLI needs a components.json and the @/* alias, which Setup has. The file lands in src/components/moonarc/.

Source

The whole component. Self-contained styles in a cascade layer so your classes always win; it imports nothing.

Pattern.astro
---
/**
 * Pattern — a repeating pattern behind a box's content: stripes, diagonals, checks, crosses, zigzags, waves or
 * hexagons, drawn by CSS gradients or one small inline SVG tile, in one colour at a strength, and faded towards an edge
 * or the middle if asked. It fills the positioned box it is placed in, behind the content, and never takes a click.
 * DotPattern and GridPattern draw dots and a grid; this draws the rest. Zero JavaScript.
 */
import type { HTMLAttributes } from 'astro/types';

type Kind = 'stripes' | 'diagonal' | 'checks' | 'crosses' | 'zigzag' | 'waves' | 'hexagons';

interface Props extends HTMLAttributes<'div'> {
  kind?: Kind;
  /** The tile's size, any CSS length. */
  size?: string;
  /** The pattern's colour, any CSS colour. */
  colour?: string;
  /** How strong it is, 0 to 1. */
  strength?: number;
  /** Fade it out: towards the bottom, the edges (from the middle) or not at all. */
  fade?: 'none' | 'bottom' | 'edges';
}

const { kind = 'diagonal', size = '24px', colour = 'currentColor', strength = 0.12, fade = 'none', class: className, style, ...rest } = Astro.props;
// the shapes the gradients cannot draw cleanly, as one tile each; the stroke is the mask, the colour the background
const tiles: Partial<Record<Kind, string>> = {
  zigzag: "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 12'><path d='M0 9l6-6 6 6 6-6 6 6' fill='none' stroke='#000' stroke-width='1.6'/></svg>",
  waves: "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 12'><path d='M0 6c3 0 3-4 6-4s3 4 6 4 3 4 6 4 3-4 6-4' fill='none' stroke='#000' stroke-width='1.6'/></svg>",
  hexagons: "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 41.57'><path d='M12 0l12 6.93v13.86L12 27.71 0 20.79V6.93zM12 27.71v13.86M0 41.57' fill='none' stroke='#000' stroke-width='1.2'/></svg>",
};
const tile = tiles[kind];
const vars = [
  `--ma-pat-size:${size}`,
  `--ma-pat-colour:${colour}`,
  `--ma-pat-strength:${strength}`,
  tile ? `--ma-pat-tile:url("data:image/svg+xml,${encodeURIComponent(tile)}")` : '',
  typeof style === 'string' ? style : '',
].filter(Boolean).join(';');
---

<div class:list={['ma-pattern', className]} data-kind={kind} data-fade={fade === 'none' ? undefined : fade} aria-hidden="true" style={vars} {...rest}></div>

<style is:global>
  @layer components {
    :where(.ma-pattern) {
      --ma-pat-ink: color-mix(in oklab, var(--ma-pat-colour, currentColor) calc(var(--ma-pat-strength, 0.12) * 100%), transparent);
      position: absolute;
      inset: 0;
      z-index: 0;
      pointer-events: none;
    }
    :where(.ma-pattern[data-kind='stripes']) { background: repeating-linear-gradient(90deg, var(--ma-pat-ink) 0 calc(var(--ma-pat-size) / 2), transparent 0 var(--ma-pat-size)); }
    :where(.ma-pattern[data-kind='diagonal']) { background: repeating-linear-gradient(45deg, var(--ma-pat-ink) 0 1.5px, transparent 0 calc(var(--ma-pat-size) / 2)); }
    :where(.ma-pattern[data-kind='checks']) { background: repeating-conic-gradient(var(--ma-pat-ink) 0 25%, transparent 0 50%) 0 0 / var(--ma-pat-size) var(--ma-pat-size); }
    /* crosses: both lines tiled across the box, masked down to a small disc in the middle of each tile */
    :where(.ma-pattern[data-kind='crosses']) {
      background-image:
        linear-gradient(transparent calc(50% - 0.75px), var(--ma-pat-ink) 0 calc(50% + 0.75px), transparent 0),
        linear-gradient(90deg, transparent calc(50% - 0.75px), var(--ma-pat-ink) 0 calc(50% + 0.75px), transparent 0);
      background-size: var(--ma-pat-size) var(--ma-pat-size);
      mask: radial-gradient(circle, #000 calc(var(--ma-pat-size) * 0.22), transparent calc(var(--ma-pat-size) * 0.22 + 0.5px)) 0 0 / var(--ma-pat-size) var(--ma-pat-size);
    }
    /* the SVG tiles: the stroke masks a layer painted the pattern's colour */
    :where(.ma-pattern:is([data-kind='zigzag'], [data-kind='waves'], [data-kind='hexagons'])) {
      background: var(--ma-pat-ink);
      mask: var(--ma-pat-tile) 0 0 / var(--ma-pat-size) auto repeat;
    }
    /* the fades are a second mask layer, intersected with the first */
    :where(.ma-pattern[data-fade='bottom']) { mask-image: var(--ma-pat-tile, linear-gradient(#000, #000)), linear-gradient(#000 20%, transparent); mask-size: var(--ma-pat-size) auto, 100% 100%; mask-repeat: repeat, no-repeat; mask-composite: intersect; }
    :where(.ma-pattern[data-fade='edges']) { mask-image: var(--ma-pat-tile, linear-gradient(#000, #000)), radial-gradient(closest-side, #000 30%, transparent); mask-size: var(--ma-pat-size) auto, 100% 100%; mask-repeat: repeat, no-repeat; mask-composite: intersect; }
    :where(.ma-pattern[data-kind='crosses'][data-fade]) { mask-image: radial-gradient(circle, #000 calc(var(--ma-pat-size) * 0.22), transparent calc(var(--ma-pat-size) * 0.22 + 0.5px)), var(--_fade); mask-size: var(--ma-pat-size) var(--ma-pat-size), 100% 100%; }
    :where(.ma-pattern[data-kind='crosses'][data-fade='bottom']) { --_fade: linear-gradient(#000 20%, transparent); }
    :where(.ma-pattern[data-kind='crosses'][data-fade='edges']) { --_fade: radial-gradient(closest-side, #000 30%, transparent); }
    /* prefers-reduced-motion: nothing here moves */
  }
</style>

Props

PropTypeDefaultDescription
kind'stripes' | 'diagonal' | 'checks' | 'crosses' | 'zigzag' | 'waves' | 'hexagons''diagonal'The pattern.
sizestring'24px'The tile's size, a CSS length.
colourstring'currentColor'The pattern's colour.
strengthnumber0.12How strong it is, 0 to 1.
fade'none' | 'bottom' | 'edges''none'Fade it towards the bottom, or from the middle to the edges.

Reduced motion

Nothing in it moves.

With ClientRouter

No script: it works on any page however it was reached.

Why it is built this way

Replaces: Pattern backgrounds (Magic UI) · Background patterns (Animata). See the migration table.

Dot Pattern

Backgrounds

A field of dots behind content, faded toward the edges, with an optional slow pulse.

every browseralways

Grid Pattern

Backgrounds

A hairline grid behind content, faded toward the edges, optionally drifting one cell at a time.

every browseralways

Noise

Backgrounds

Film grain over a gradient or image, blended and flickering at 12 fps.

every browseralways

Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown