Composables
ComposableOriginal

Marching Border

Animated dashed border overlay that conforms to its parent's corner radius

MarchingBorder is an animated dashed border overlay that conforms to its parent's corner radius. Use it to signal edit states, pending changes, or drag-over targets without shifting layout, since it sits outside the parent's box model.

Preview

Bundle: Reading list

3 unsaved changes. Confirm to publish, or discard to revert.

Installation

Usage

MarchingBorder is an absolutely-positioned SVG overlay. Put it inside a positioned parent (anything with position: relative, or another positioned ancestor) and it picks up that element's border-radius automatically, so the dashes follow rounded corners. It sits outside the parent's box model, so showing or hiding it never shifts layout. Handy for edit states, pending changes, and drag-over indicators.

The stroke uses currentColor, so set the color the same way you'd set text color: a text-* class, a color style, or inheriting from an ancestor.

Examples

Variants

Adjust dash, gap, strokeWidth, and duration to tune the pattern; dash and gap are percentages of the perimeter, so it tiles evenly at any size.

Default
Fine + fast
Chunky + slow

Overriding the radius

Set a border-radius on the overlay itself when it should differ from the parent, or when the parent has none (a canvas, a square panel); by default it inherits the parent's corners.

Inherited (square)
rounded-[24px]

API Reference

MarchingBorder is a single SVG component. It accepts any native <svg> prop in addition to those documented below; spread props land on the root <svg> element.

Props

Prop

Notes

  • Parent positioning. The SVG is absolute inset-0, so the parent needs to be positioned (relative, absolute, fixed, or sticky). Otherwise the border attaches to the nearest positioned ancestor.
  • Seam-free loop. pathLength is rounded to a whole multiple of dash + gap, so the pattern tiles an exact number of times at any size. The path starts at the middle of the top edge, so any subpixel seam lands on a straight side instead of a corner.
  • Shared keyframe. One @keyframes dash-march rule is shared across every instance. The end offset comes from a per-instance CSS variable, so changing dash or gap doesn't generate new keyframes.
  • Reduced motion. Under prefers-reduced-motion: reduce the animation stops but the dashes still render, since the static border is the actual signal and shouldn't vanish.
  • Color. The stroke is currentColor. Set the color like text color, for example a text-* class such as text-primary or text-destructive on the component.