A notched pixel frame with a snake crawling around it one block at a time, like a handheld game from 1998. The snake is an SVG dash on steps() timing with crisp edges, so every block stays square.
Also known as: pixel border css, retro pixel border animation, 8bit border css.
<div class="bdr-pixel-border">
<svg class="bdr-pixel-border-svg" viewBox="0 0 30 17" preserveAspectRatio="none" aria-hidden="true">
<path class="bdr-pixel-border-frame" d="M1.5.5H28.5L29.5 1.5V15.5L28.5 16.5H1.5L.5 15.5V1.5Z" pathLength="88"/>
<path class="bdr-pixel-border-snake" d="M1.5.5H28.5L29.5 1.5V15.5L28.5 16.5H1.5L.5 15.5V1.5Z" pathLength="88"/>
<path class="bdr-pixel-border-head" d="M1.5.5H28.5L29.5 1.5V15.5L28.5 16.5H1.5L.5 15.5V1.5Z" pathLength="88"/>
</svg>
<span class="bdr-pixel-border-text">Press start</span>
</div>.bdr-pixel-border {
--c: var(--bdr-color, #3b82f6);
font-size: var(--bdr-size, 16px);
position: relative;
display: grid;
place-items: center;
width: 15em;
height: 8.5em;
border-radius: 1em;
font-family: system-ui, sans-serif;
font-weight: 600;
border-radius: 0;
font-family: ui-monospace, Menlo, Consolas, monospace;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.bdr-pixel-border-svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
shape-rendering: crispEdges;
pointer-events: none;
}
.bdr-pixel-border-svg path {
fill: none;
stroke-width: 1;
}
.bdr-pixel-border-frame {
stroke: color-mix(in srgb, var(--c) 22%, transparent);
stroke-dasharray: 0.85 0.15;
}
.bdr-pixel-border-snake {
stroke: var(--c);
stroke-dasharray: 0.85 0.15 0.85 0.15 0.85 0.15 0.85 0.15 0.85 0.15 0.85 82.15;
animation: bdr-pixel-border-crawl 5.5s steps(88) infinite;
}
.bdr-pixel-border-head {
stroke: color-mix(in srgb, var(--c) 50%, currentColor);
stroke-dasharray: 0.85 87.15;
animation: bdr-pixel-border-crawl-head 5.5s steps(88) infinite;
}
.bdr-pixel-border-text { animation: bdr-pixel-border-blink 1s step-end infinite; }
/* 88 blocks around the frame, one block per step. The head is block 6, so it runs 5 ahead. */
@keyframes bdr-pixel-border-crawl {
from { stroke-dashoffset: 0; }
to { stroke-dashoffset: -88; }
}
@keyframes bdr-pixel-border-crawl-head {
from { stroke-dashoffset: -5; }
to { stroke-dashoffset: -93; }
}
@keyframes bdr-pixel-border-blink {
50% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.bdr-pixel-border, .bdr-pixel-border *, .bdr-pixel-border::before, .bdr-pixel-border::after, .bdr-pixel-border *::before, .bdr-pixel-border *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--bdr-color on the element or a parent.
Currently previewing #3b82f6.--bdr-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 16px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.