CSS QR Code
A working QR code that scans to purecss.com, drawn with CSS box-shadow on a single element, with no images, no SVG and no JavaScript.
- Level
- Advanced
- Published
- Updated
- Reading time
- 8 min
Try it
3 live demos
One <div>, and every pixel is a CSS box-shadow value
One element, two hundred seventeen shadows
Other ways to build it
A sprite that scales from one value
See the codeThe same idea with background layers
See the codeBuild it
6 snippets to copy
CSS QR Code
HTML
<div class="qr-stack">
<div class="qr-wrap">
<!-- the entire QR code is this one element -->
<div class="qr-pixel" role="img" aria-label="QR code linking to purecss.com"></div>
<span class="qr-label">purecss.com</span>
</div>
<p class="qr-caption">One <code><div></code>, and every pixel is a CSS <code>box-shadow</code> value</p>
</div>
CSS
.qr-stack {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
}
.qr-wrap {
background: #ffffff;
border-radius: 12px;
/* quiet zone: 4 modules (40px) of white on every side */
padding: 40px;
display: inline-flex;
flex-direction: column;
align-items: center;
/* keeps the label out of the quiet zone below the code */
gap: 40px;
/* contrast themes would drop every shadow; keep the code's own colors */
forced-color-adjust: none;
}
.qr-pixel {
/* Each box-shadow is one 10x10 module; blur and spread stay 0 for sharp pixels */
width: 10px;
height: 10px;
/* Shadows draw outside the box and add no layout, so reserve the 21x21 grid */
margin: 0 200px 200px 0;
/* the box itself is module 0,0, the corner of the top-left finder */
background: #1a1a1a;
border-radius: 0;
/* HTTPS://PURECSS.COM as QR version 1, error correction M, mask 0:
one shadow per dark module, 217 of them */
box-shadow:
/* Finder: top-left 7x7 */
10px 0 0 0 #1a1a1a, 20px 0 0 0 #1a1a1a, 30px 0 0 0 #1a1a1a, 40px 0 0 0 #1a1a1a, 50px 0 0 0 #1a1a1a, 60px 0 0 0 #1a1a1a,
0 10px 0 0 #1a1a1a, 60px 10px 0 0 #1a1a1a,
0 20px 0 0 #1a1a1a, 20px 20px 0 0 #1a1a1a, 30px 20px 0 0 #1a1a1a, 40px 20px 0 0 #1a1a1a, 60px 20px 0 0 #1a1a1a,
0 30px 0 0 #1a1a1a, 20px 30px 0 0 #1a1a1a, 30px 30px 0 0 #1a1a1a, 40px 30px 0 0 #1a1a1a, 60px 30px 0 0 #1a1a1a,
0 40px 0 0 #1a1a1a, 20px 40px 0 0 #1a1a1a, 30px 40px 0 0 #1a1a1a, 40px 40px 0 0 #1a1a1a, 60px 40px 0 0 #1a1a1a,
0 50px 0 0 #1a1a1a, 60px 50px 0 0 #1a1a1a,
0 60px 0 0 #1a1a1a, 10px 60px 0 0 #1a1a1a, 20px 60px 0 0 #1a1a1a, 30px 60px 0 0 #1a1a1a, 40px 60px 0 0 #1a1a1a, 50px 60px 0 0 #1a1a1a, 60px 60px 0 0 #1a1a1a,
/* Finder: top-right 7x7 */
140px 0 0 0 #1a1a1a, 150px 0 0 0 #1a1a1a, 160px 0 0 0 #1a1a1a, 170px 0 0 0 #1a1a1a, 180px 0 0 0 #1a1a1a, 190px 0 0 0 #1a1a1a, 200px 0 0 0 #1a1a1a,
140px 10px 0 0 #1a1a1a, 200px 10px 0 0 #1a1a1a,
140px 20px 0 0 #1a1a1a, 160px 20px 0 0 #1a1a1a, 170px 20px 0 0 #1a1a1a, 180px 20px 0 0 #1a1a1a, 200px 20px 0 0 #1a1a1a,
140px 30px 0 0 #1a1a1a, 160px 30px 0 0 #1a1a1a, 170px 30px 0 0 #1a1a1a, 180px 30px 0 0 #1a1a1a, 200px 30px 0 0 #1a1a1a,
140px 40px 0 0 #1a1a1a, 160px 40px 0 0 #1a1a1a, 170px 40px 0 0 #1a1a1a, 180px 40px 0 0 #1a1a1a, 200px 40px 0 0 #1a1a1a,
140px 50px 0 0 #1a1a1a, 200px 50px 0 0 #1a1a1a,
140px 60px 0 0 #1a1a1a, 150px 60px 0 0 #1a1a1a, 160px 60px 0 0 #1a1a1a, 170px 60px 0 0 #1a1a1a, 180px 60px 0 0 #1a1a1a, 190px 60px 0 0 #1a1a1a, 200px 60px 0 0 #1a1a1a,
/* Finder: bottom-left 7x7 */
0 140px 0 0 #1a1a1a, 10px 140px 0 0 #1a1a1a, 20px 140px 0 0 #1a1a1a, 30px 140px 0 0 #1a1a1a, 40px 140px 0 0 #1a1a1a, 50px 140px 0 0 #1a1a1a, 60px 140px 0 0 #1a1a1a,
0 150px 0 0 #1a1a1a, 60px 150px 0 0 #1a1a1a,
0 160px 0 0 #1a1a1a, 20px 160px 0 0 #1a1a1a, 30px 160px 0 0 #1a1a1a, 40px 160px 0 0 #1a1a1a, 60px 160px 0 0 #1a1a1a,
0 170px 0 0 #1a1a1a, 20px 170px 0 0 #1a1a1a, 30px 170px 0 0 #1a1a1a, 40px 170px 0 0 #1a1a1a, 60px 170px 0 0 #1a1a1a,
0 180px 0 0 #1a1a1a, 20px 180px 0 0 #1a1a1a, 30px 180px 0 0 #1a1a1a, 40px 180px 0 0 #1a1a1a, 60px 180px 0 0 #1a1a1a,
0 190px 0 0 #1a1a1a, 60px 190px 0 0 #1a1a1a,
0 200px 0 0 #1a1a1a, 10px 200px 0 0 #1a1a1a, 20px 200px 0 0 #1a1a1a, 30px 200px 0 0 #1a1a1a, 40px 200px 0 0 #1a1a1a, 50px 200px 0 0 #1a1a1a, 60px 200px 0 0 #1a1a1a,
/* Timing: row 6 and column 6 */
80px 60px 0 0 #1a1a1a, 100px 60px 0 0 #1a1a1a, 120px 60px 0 0 #1a1a1a,
60px 80px 0 0 #1a1a1a,
60px 100px 0 0 #1a1a1a,
60px 120px 0 0 #1a1a1a,
/* Dark module: always on, beside the bottom-left finder */
80px 130px 0 0 #1a1a1a,
/* Format information: level M, mask 0, written twice */
80px 10px 0 0 #1a1a1a,
80px 40px 0 0 #1a1a1a,
0 80px 0 0 #1a1a1a, 20px 80px 0 0 #1a1a1a, 40px 80px 0 0 #1a1a1a, 160px 80px 0 0 #1a1a1a, 190px 80px 0 0 #1a1a1a,
80px 160px 0 0 #1a1a1a,
80px 180px 0 0 #1a1a1a,
80px 200px 0 0 #1a1a1a,
/* Data and error correction */
90px 0 0 0 #1a1a1a, 100px 0 0 0 #1a1a1a, 110px 0 0 0 #1a1a1a,
110px 10px 0 0 #1a1a1a,
100px 20px 0 0 #1a1a1a, 120px 20px 0 0 #1a1a1a,
100px 30px 0 0 #1a1a1a, 110px 30px 0 0 #1a1a1a,
100px 40px 0 0 #1a1a1a, 120px 40px 0 0 #1a1a1a,
90px 50px 0 0 #1a1a1a, 100px 50px 0 0 #1a1a1a, 110px 50px 0 0 #1a1a1a,
90px 70px 0 0 #1a1a1a, 110px 70px 0 0 #1a1a1a,
90px 80px 0 0 #1a1a1a, 120px 80px 0 0 #1a1a1a,
10px 90px 0 0 #1a1a1a, 20px 90px 0 0 #1a1a1a, 30px 90px 0 0 #1a1a1a, 40px 90px 0 0 #1a1a1a, 70px 90px 0 0 #1a1a1a, 80px 90px 0 0 #1a1a1a, 90px 90px 0 0 #1a1a1a,
110px 90px 0 0 #1a1a1a, 130px 90px 0 0 #1a1a1a, 140px 90px 0 0 #1a1a1a, 170px 90px 0 0 #1a1a1a, 180px 90px 0 0 #1a1a1a, 190px 90px 0 0 #1a1a1a, 200px 90px 0 0 #1a1a1a,
0 100px 0 0 #1a1a1a, 10px 100px 0 0 #1a1a1a, 20px 100px 0 0 #1a1a1a, 40px 100px 0 0 #1a1a1a, 70px 100px 0 0 #1a1a1a, 80px 100px 0 0 #1a1a1a, 90px 100px 0 0 #1a1a1a,
100px 100px 0 0 #1a1a1a, 130px 100px 0 0 #1a1a1a, 140px 100px 0 0 #1a1a1a,
20px 110px 0 0 #1a1a1a, 50px 110px 0 0 #1a1a1a, 80px 110px 0 0 #1a1a1a, 90px 110px 0 0 #1a1a1a, 100px 110px 0 0 #1a1a1a, 130px 110px 0 0 #1a1a1a, 140px 110px 0 0 #1a1a1a,
160px 110px 0 0 #1a1a1a,
0 120px 0 0 #1a1a1a, 30px 120px 0 0 #1a1a1a, 50px 120px 0 0 #1a1a1a, 70px 120px 0 0 #1a1a1a, 80px 120px 0 0 #1a1a1a, 90px 120px 0 0 #1a1a1a, 110px 120px 0 0 #1a1a1a,
120px 120px 0 0 #1a1a1a, 160px 120px 0 0 #1a1a1a, 200px 120px 0 0 #1a1a1a,
90px 130px 0 0 #1a1a1a, 100px 130px 0 0 #1a1a1a, 110px 130px 0 0 #1a1a1a, 130px 130px 0 0 #1a1a1a, 140px 130px 0 0 #1a1a1a, 150px 130px 0 0 #1a1a1a, 200px 130px 0 0 #1a1a1a,
90px 140px 0 0 #1a1a1a, 100px 140px 0 0 #1a1a1a, 110px 140px 0 0 #1a1a1a, 120px 140px 0 0 #1a1a1a, 140px 140px 0 0 #1a1a1a, 170px 140px 0 0 #1a1a1a, 180px 140px 0 0 #1a1a1a,
90px 150px 0 0 #1a1a1a, 120px 150px 0 0 #1a1a1a, 140px 150px 0 0 #1a1a1a, 150px 150px 0 0 #1a1a1a,
120px 160px 0 0 #1a1a1a, 150px 160px 0 0 #1a1a1a, 160px 160px 0 0 #1a1a1a, 180px 160px 0 0 #1a1a1a,
100px 170px 0 0 #1a1a1a, 110px 170px 0 0 #1a1a1a, 120px 170px 0 0 #1a1a1a, 130px 170px 0 0 #1a1a1a, 190px 170px 0 0 #1a1a1a,
120px 180px 0 0 #1a1a1a, 150px 180px 0 0 #1a1a1a, 200px 180px 0 0 #1a1a1a,
90px 190px 0 0 #1a1a1a, 100px 190px 0 0 #1a1a1a, 120px 190px 0 0 #1a1a1a, 130px 190px 0 0 #1a1a1a, 140px 190px 0 0 #1a1a1a, 170px 190px 0 0 #1a1a1a, 190px 190px 0 0 #1a1a1a,
200px 190px 0 0 #1a1a1a,
100px 200px 0 0 #1a1a1a, 140px 200px 0 0 #1a1a1a, 150px 200px 0 0 #1a1a1a, 170px 200px 0 0 #1a1a1a, 200px 200px 0 0 #1a1a1a;
}
.qr-label {
font-family: "DM Mono", "Fira Code", Consolas, monospace;
font-size: .65rem;
letter-spacing: .1em;
text-transform: uppercase;
color: #666;
}
.qr-caption {
font-family: "DM Mono", "Fira Code", Consolas, monospace;
font-size: .7rem;
color: #88888f;
letter-spacing: .04em;
text-align: center;
margin: 0;
}
A sprite that scales from one value
Writing offsets as multiples of a custom property makes the whole drawing resizable by changing a single declaration. All three of these share one shadow list. Only --px differs, and it drives the cell size, every offset, and the margin that reserves the area. The one on the right also sets border-radius: 50%, which every shadow picks up at once, so the same forty six cells become forty six dots.
HTML
<div class="qr-sprite" style="--px: 5px"></div>
<div class="qr-sprite" style="--px: 8px"></div>
<div class="qr-sprite qr-sprite-round" style="--px: 8px"></div>
CSS
.qr-sprite {
--px: 8px;
--pxc: #b8ff57;
/* one column step per name, so an offset is a name and nothing else */
--p1: calc(var(--px) * 1);
--p2: calc(var(--px) * 2);
--p3: calc(var(--px) * 3);
--p4: calc(var(--px) * 4);
--p5: calc(var(--px) * 5);
--p6: calc(var(--px) * 6);
--p7: calc(var(--px) * 7);
--p8: calc(var(--px) * 8);
--p9: calc(var(--px) * 9);
--p10: calc(var(--px) * 10);
width: var(--px);
height: var(--px);
/* the sprite is 11 cells wide and 8 tall, and none of that is layout,
so the margin has to hold the area open by hand */
margin: 0 var(--p10) var(--p7) 0;
/* cell 0,0 is empty in this drawing, so the source box paints nothing */
background: transparent;
border-radius: 0;
box-shadow:
var(--p2) 0 0 0 var(--pxc), var(--p8) 0 0 0 var(--pxc),
var(--p3) var(--p1) 0 0 var(--pxc), var(--p7) var(--p1) 0 0 var(--pxc),
var(--p2) var(--p2) 0 0 var(--pxc), var(--p3) var(--p2) 0 0 var(--pxc),
var(--p4) var(--p2) 0 0 var(--pxc), var(--p5) var(--p2) 0 0 var(--pxc),
var(--p6) var(--p2) 0 0 var(--pxc), var(--p7) var(--p2) 0 0 var(--pxc),
var(--p8) var(--p2) 0 0 var(--pxc),
var(--p1) var(--p3) 0 0 var(--pxc), var(--p2) var(--p3) 0 0 var(--pxc),
var(--p4) var(--p3) 0 0 var(--pxc), var(--p5) var(--p3) 0 0 var(--pxc),
var(--p6) var(--p3) 0 0 var(--pxc), var(--p8) var(--p3) 0 0 var(--pxc),
var(--p9) var(--p3) 0 0 var(--pxc),
0 var(--p4) 0 0 var(--pxc), var(--p1) var(--p4) 0 0 var(--pxc),
var(--p2) var(--p4) 0 0 var(--pxc), var(--p3) var(--p4) 0 0 var(--pxc),
var(--p4) var(--p4) 0 0 var(--pxc), var(--p5) var(--p4) 0 0 var(--pxc),
var(--p6) var(--p4) 0 0 var(--pxc), var(--p7) var(--p4) 0 0 var(--pxc),
var(--p8) var(--p4) 0 0 var(--pxc), var(--p9) var(--p4) 0 0 var(--pxc),
var(--p10) var(--p4) 0 0 var(--pxc),
0 var(--p5) 0 0 var(--pxc), var(--p2) var(--p5) 0 0 var(--pxc),
var(--p3) var(--p5) 0 0 var(--pxc), var(--p4) var(--p5) 0 0 var(--pxc),
var(--p5) var(--p5) 0 0 var(--pxc), var(--p6) var(--p5) 0 0 var(--pxc),
var(--p7) var(--p5) 0 0 var(--pxc), var(--p8) var(--p5) 0 0 var(--pxc),
var(--p10) var(--p5) 0 0 var(--pxc),
0 var(--p6) 0 0 var(--pxc), var(--p2) var(--p6) 0 0 var(--pxc),
var(--p8) var(--p6) 0 0 var(--pxc), var(--p10) var(--p6) 0 0 var(--pxc),
var(--p3) var(--p7) 0 0 var(--pxc), var(--p4) var(--p7) 0 0 var(--pxc),
var(--p6) var(--p7) 0 0 var(--pxc), var(--p7) var(--p7) 0 0 var(--pxc);
}
/* one property, forty six round cells */
.qr-sprite-round {
border-radius: 50%;
}
The same idea with background layers
Backgrounds draw inside the element's own box, so this version needs no margin trick and cannot overflow. Three finder marks are stacked from pairs of solid color gradients, and the data field is a single tiled dot that covers whatever area the element happens to have. Earlier layers paint on top of later ones, which is why the finder marks sit over the tile rather than under it. The trade off is that a tile repeats by definition, so an arbitrary non repeating pattern still wants shadows.
HTML
<div class="qr-stack">
<div class="qr-wrap">
<div class="qr-grad"></div>
<span class="qr-label">drawn from 10 layers</span>
</div>
</div>
CSS
.qr-grad {
width: 168px;
height: 168px;
background-color: #ffffff;
/* first listed paints on top: cores, then rings, then the data tile */
background-image:
linear-gradient(#1a1a1a, #1a1a1a),
linear-gradient(#1a1a1a, #1a1a1a),
linear-gradient(#1a1a1a, #1a1a1a),
linear-gradient(#ffffff, #ffffff),
linear-gradient(#ffffff, #ffffff),
linear-gradient(#ffffff, #ffffff),
linear-gradient(#1a1a1a, #1a1a1a),
linear-gradient(#1a1a1a, #1a1a1a),
linear-gradient(#1a1a1a, #1a1a1a),
radial-gradient(#1a1a1a 38%, transparent 40%);
background-repeat:
no-repeat, no-repeat, no-repeat,
no-repeat, no-repeat, no-repeat,
no-repeat, no-repeat, no-repeat, repeat;
background-size:
18px 18px, 18px 18px, 18px 18px,
30px 30px, 30px 30px, 30px 30px,
42px 42px, 42px 42px, 42px 42px, 14px 14px;
background-position:
12px 12px, calc(100% - 12px) 12px, 12px calc(100% - 12px),
6px 6px, calc(100% - 6px) 6px, 6px calc(100% - 6px),
0 0, 100% 0, 0 100%,
0 0;
}
Understand it
How it works and 5 properties
A CSS QR code is a pixel grid painted entirely from box-shadow. The property takes a comma separated list with no upper limit, and with blur and spread both at zero every entry is an exact copy of the element's own box in a new position. One ten pixel square plus a list of offsets is enough to draw a real scannable code from a single empty <div>.
The catch is that shadows are invisible to layout, so the element still measures ten pixels by ten while painting two hundred ten by two hundred ten. Two further versions follow: a pixel sprite whose whole size is driven by one custom property, and a gradient look-alike drawn from tiled background-image layers, which does not scan.
How it works
box-shadow accepts an unlimited list of shadows, each an independent colored rectangle offset from the element. With blur: 0 and spread: 0, each shadow is exactly the same size as the element, which makes it a sharp square cell. A 10px element at the top left plus shadows at multiples of 10px paints a contiguous grid with one element and no images. The code on this page is QR version 1, which encodes HTTPS://PURECSS.COM in alphanumeric mode with error correction level M.
Each shadow in the list is drawn as the element's border box shape, offset by the first two lengths, resized by the spread, and softened by the blur. Set blur and spread to zero and the copy is pixel identical to the source, so the offsets alone decide where cells land. Because the offsets here are multiples of the element's own 10px size, the cells touch edge to edge and the pattern reads as one solid image rather than a scatter of squares.
The part that catches people out is that box-shadow contributes nothing to layout. It is painted after the box model has already decided how much room the element needs, and the box model only ever counted the original ten by ten square. The code therefore spills out of its container and overlaps whatever follows it. The fix in this stylesheet is margin: 0 200px 200px 0, which reserves exactly the area the shadows will occupy. Without those two values the grid overflows its box.
Shadows inherit the element's border-radius, all of them, which is worth knowing before reusing the technique. Set border-radius: 50% on the source element and every shadow becomes a circle, which is how the star field in CSS geometric art is drawn from two dots and a dozen offsets. Here the radius is pinned to 0 so the cells stay square and meet cleanly.
One element is not the same as one paint operation. Two hundred seventeen shadows means two hundred seventeen rasterized rectangles every time the element repaints, and the cost grows linearly with the list. For a fixed code that never animates this is irrelevant. For anything that moves, an SVG or a single image file is lighter, because it is one draw instead of hundreds.
The code on this page is a real QR code. It encodes HTTPS://PURECSS.COM in alphanumeric mode, which packs two characters into 11 bits instead of spending 8 bits per character in byte mode. Version 1 is 21 by 21 modules, and at error correction level M the code can lose about 15% of its area and still decode. The three finder patterns, the timing patterns along row and column 6, the format information, and the data and error correction codewords are all exactly where the spec puts them. The jsQR library decodes it from screenshots of the rendered page. Version 1 has no alignment patterns, which only appear in version 2 and up.
Accessibility notes
The code carries role="img" and aria-label="QR code linking to purecss.com" so it is exposed as an image with that name. The visible label below the code gives the destination in plain text as well, so the information is available whether someone scans the code, reads the label, or uses assistive technology.
The code stays visible and scannable in forced colors mode, which is not automatic. Windows contrast themes set box-shadow: none on everything by default, which would erase every module. The card that holds the code carries forced-color-adjust: none, which keeps the code's own colors in place. The property is inherited, so the gradient variant below shares that setting.
The code survives page zoom without pixelating, since every module is redrawn at the new size rather than stretched from pixels. Hairline seams between neighboring dark cells can appear at some fractional scales where the browser cannot fit an exact number of device pixels into each module. At those scales a decoder may fail on the raw pixels but succeed after a slight blur, which is roughly what a camera does to a screen.
Mistakes worth avoiding
- Forgetting that shadows add nothing to layout. The grid paints outside the element's box, overflows its container, and is clipped or overlapped, and the element still reports ten pixels square in the inspector.
- Leaving
blurorspreadat a non zero value. The cells get soft edges and bleed into each other, so the pattern turns into a gray smear instead of a grid. - Rounding the source element.
border-radiusapplies to every shadow at once, so one property turns the entire grid into circles. - Choosing offsets that are not multiples of the element size. Cells either overlap or leave hairlines between them. Hairlines can also appear at fractional scale factors even when every offset is correct, where the browser cannot fit an exact number of device pixels into each cell.
- Editing cells by hand instead of regenerating. A real code is laid out by an encoder and masked, so moving one shadow breaks the data.
- Shrinking the quiet zone or placing content inside it. Scanners need at least four modules of light margin on every side to find the code.
- Using theme colors that can invert the code. Dark modules on a light background is the standard polarity. Not every scanner reads inverted codes.
Ship it
5 features in 4 browsers, 5 questions
Browser support
| Feature | ||||
|---|---|---|---|---|
box-shadow | 4 | 3.5 | 5 | 12 |
border-radius | 4 | 3 | 3.1 | 12 |
multiple backgrounds | 4 | 3.6 | 3.1 | 12 |
gradients | 10 | 36 | 15.4 | 12 |
custom properties | 49 | 31 | 10 | 16 |
Figures come from Can I Use for CSS Box-shadow, Border-radius, Multiple backgrounds, CSS Gradients and CSS Variables. Nothing on this page needs a recent browser. The gradient row reads later than the others because Can I Use only counts a browser as fully supporting gradients once the whole modern syntax is in place, and the simple two stop forms used here have worked for far longer than that figure suggests.
What you can build with it
- Ticket and badge mockups. Design comps and print previews. A real code can be drawn directly in the stylesheet when the destination is known, or a placeholder grid can hold the space while the real data comes later.
- Stylesheet-generated codes. Pages or badges where the destination is fixed and known at build time, so the code can live in the stylesheet rather than being generated per request.
- Decorative pixel motifs. Logos, icons and sprites drawn from a single element, which keeps them in the stylesheet rather than in the asset pipeline.
- Email safe patterns. Some clients strip images but keep inline styles, so a shadow drawn motif renders where an image would show a broken frame.
- Print styling. A pattern that is part of the stylesheet rather than a remote asset, so it does not depend on image loading rules in a print context.
Frequently asked questions
Can a CSS QR code be scanned?
HTTPS://PURECSS.COM. It is a real QR code with all the required parts: three finder patterns, timing patterns, format information, and data and error correction codewords. Version 1 has no alignment patterns, which only appear in version 2 and up. Screenshots of the page at 1440px, 390px and 360px widths in both site themes decoded to the correct URL with jsQR, as did page zoom at 100%, 110%, 150%, 200% and 300%.Why does my box-shadow pattern overflow its container?
width and height, so the browser reserves no room for the shadows. Add margin on the right and bottom equal to the largest offset in the list.How many box-shadows can one element have?
box-shadow layers one element can carry. The practical limit is paint cost, since every entry is another rectangle rasterized on each repaint. A few dozen is unnoticeable. The code on this page uses two hundred seventeen shadows and never animates, so the cost is irrelevant. Several hundred on an element that moves every frame will show up as dropped frames.Is box-shadow or background-image better for drawing patterns?
How do I resize a box-shadow pixel grid?
calc() expressions over a custom property, then change the one value. The sprite variant on this page sets --px on the element and every offset is a multiple of it, so the whole drawing scales from a single declaration.