Dot Pattern
BackgroundsA field of dots behind content, faded toward the edges, with an optional slow pulse.
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, from the library itself: scroll, hover, navigate away and back. The demo is never gated.
Adjust · props are custom properties, set inline, so you edit the running instance
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
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.
One command adds the integration, the base tokens and every component. Then import what you use.
npx astro add moonarcpnpm astro add moonarcbunx astro add moonarc---
import Pattern from '@moonarc/core/Pattern';
---
<section style="position: relative">
<Pattern kind="diagonal" size="18px" strength={0.1} fade="bottom" />
<h2>Pricing</h2>
</section>Owns the file, no dependency. The registry item also installs the base tokens.
npx shadcn@latest add https://moonarc.dev/r/pattern.jsonThe CLI needs a components.json and the @/* alias, which Setup has. The file lands in src/components/moonarc/.
The whole component. Self-contained styles in a cascade layer so your classes always win; it imports nothing.
---
/**
* 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>| 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. |
Nothing in it moves.
ClientRouterNo script: it works on any page however it was reached.
Replaces: Pattern backgrounds (Magic UI) · Background patterns (Animata). See the migration table.
A field of dots behind content, faded toward the edges, with an optional slow pulse.
A hairline grid behind content, faded toward the edges, optionally drifting one cell at a time.
Film grain over a gradient or image, blended and flickering at 12 fps.
Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown