# Pattern (Moonarc)

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.

- Import: `import Pattern from '@moonarc/core/Pattern'`
- Install: `npx astro add moonarc` · copy-paste: `npx shadcn@latest add https://moonarc.dev/r/pattern.json`
- Tier A · category background · trigger always
- Readout: `<Pattern kind="hexagons" size="28px" strength={0.15} fade="edges" />`
- Browser support: widely (Chrome 120 · Firefox 53 · Safari 15.4); elsewhere: without masks the zigzags, waves and hexagons are a flat tint and no pattern fades
- Measured cost: 0 B JS (CSS 6.2 kB raw)
- Page: https://moonarc.dev/components/pattern/

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `kind` | `'stripes' | 'diagonal' | 'checks' | 'crosses' | 'zigzag' | 'waves' | 'hexagons'` | `'diagonal'` | The pattern. |
| `size` | `string` | `'24px'` | The tile's size, a CSS length. |
| `colour` | `string` | `'currentColor'` | The pattern's colour. |
| `strength` | `number` | `0.12` | How strong it is, 0 to 1. |
| `fade` | `'none' | 'bottom' | 'edges'` | `'none'` | Fade it towards the bottom, or from the middle to the edges. |

## Usage

```astro
<section style="position: relative">
  <Pattern kind="diagonal" size="18px" strength={0.1} fade="bottom" />
  <h2>Pricing</h2>
</section>
```

## Reduced motion

Nothing in it moves.

## With ClientRouter

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

## Craft

- The colour is mixed with transparent at the strength, so the pattern takes the text colour of whatever it sits in and reads on both schemes.
- Zigzags, waves and hexagons are one SVG tile used as a mask over a layer of the colour, so changing the colour never touches the SVG.
- A fade is a second mask layer intersected with the first.

## Replaces

- Pattern backgrounds (Magic UI)
- Background patterns (Animata)

## Source

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

```
