A file drop zone with a see-through middle and a dashed outline whose gradient slowly turns while the dashes march. One SVG rect with pathLength 100 strokes a linearGradient that SMIL rotates.
Also known as: css gradient border with transparent background, gradient dashed border css, animated dashed border drop zone.
<div class="bdr-gradient-dashed-border">
<svg class="bdr-gradient-dashed-border-svg" aria-hidden="true">
<defs>
<linearGradient id="bdr-gradient-dashed-border-g" x1="0" y1="0" x2="1" y2="0">
<stop class="bdr-gradient-dashed-border-s1" offset="0"/>
<stop class="bdr-gradient-dashed-border-s2" offset="0.5"/>
<stop class="bdr-gradient-dashed-border-s3" offset="1"/>
<animateTransform attributeName="gradientTransform" type="rotate" values="0 0.5 0.5;360 0.5 0.5" dur="8s" repeatCount="indefinite"/>
</linearGradient>
</defs>
<rect class="bdr-gradient-dashed-border-rect" width="100%" height="100%" pathLength="100" stroke="url(#bdr-gradient-dashed-border-g)"/>
</svg>
<svg class="bdr-gradient-dashed-border-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 15V4m0 0L7.5 8.5M12 4l4.5 4.5M4.5 15v3a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2v-3"/></svg>
<span class="bdr-gradient-dashed-border-title">Drop files to upload</span>
<span class="bdr-gradient-dashed-border-hint">PNG, JPG or PDF up to 10 MB</span>
</div>.bdr-gradient-dashed-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;
place-content: center;
gap: 0.3em;
}
.bdr-gradient-dashed-border-svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
overflow: visible;
pointer-events: none;
}
.bdr-gradient-dashed-border-s1 { stop-color: var(--c); }
.bdr-gradient-dashed-border-s2 { stop-color: color-mix(in oklch, var(--c), #d946ef 65%); }
.bdr-gradient-dashed-border-s3 { stop-color: color-mix(in oklch, var(--c), #f59e0b 70%); }
.bdr-gradient-dashed-border-rect {
rx: 1em;
fill: none;
stroke-width: 0.14em;
stroke-linecap: round;
stroke-dasharray: 1.4 2.6;
animation: bdr-gradient-dashed-border-march 1.6s linear infinite;
}
.bdr-gradient-dashed-border-icon {
width: 1.5em;
height: 1.5em;
margin-bottom: 0.2em;
fill: none;
stroke: var(--c);
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
animation: bdr-gradient-dashed-border-nudge 1.6s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
.bdr-gradient-dashed-border-title { font-size: 0.9em; }
.bdr-gradient-dashed-border-hint {
font-size: 0.7em;
font-weight: 450;
opacity: 0.6;
}
@keyframes bdr-gradient-dashed-border-march {
to { stroke-dashoffset: -4; }
}
@keyframes bdr-gradient-dashed-border-nudge {
50% { transform: translateY(-0.15em); }
}
@media (prefers-reduced-motion: reduce) {
.bdr-gradient-dashed-border-rect,
.bdr-gradient-dashed-border-icon { animation: none; }
}
@media (prefers-reduced-motion: reduce) {
.bdr-gradient-dashed-border, .bdr-gradient-dashed-border *, .bdr-gradient-dashed-border::before, .bdr-gradient-dashed-border::after, .bdr-gradient-dashed-border *::before, .bdr-gradient-dashed-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.