Shape renders a vector graphic from path data inside a root svg: one base path, plus optional layers for marks that mix strokes and fills. Paint flows through design tokens, ChartColor, or DataColor; there is no className, style, or as. Use it for map pins, cluster badges, and other marks you author as path data rather than as a fixed glyph or raster asset.
When to use
Custom silhouettes defined in user space (pins, polygons, irregular GIS symbols) where Icon has no glyph.
Legend or map marks that need strokeAlignment inside or outside a closed path.
Marks that must show a third-party color via DataColor on fill or stroke.
Path strings are developer-owned. Validate and brand them with toSvgPathData from @prepared911/ui/shape (the helper throws with a clear message when the microsyntax is invalid). Rules enforced at authoring time:
Must start with M or m.
Only path commands MmLlHhVvCcSsQqTtAaZz and numeric arguments.
Do not cast raw strings to SvgPathData at call sites.
Store branded SvgPathData in module scope next to your asset constants, as map fixtures do, so invalid paths fail at load time rather than in the tree.
SvgViewBox
Pass a SvgViewBox object with positive width and height (import the type from @prepared911/ui/shape):
Shape renders it as "minX minY width height" on the root svg. The view box defines user units for the path; it is independent of the on-screen box (see size below).
Layers
A mark such as a route line with a dot at its start and an arrowhead at its end needs strokes and fills that differ per part. Pass layers, an ordered list of ShapeLayer objects, and each is drawn above the base path with its own fill, stroke, strokeWidth, strokeLinecap and strokeLinejoin. A layer is always stroked at the center of its path; strokeAlignment applies to the base path only.
strokeLinecap and strokeLinejoin also apply to the base path and default to Butt and Miter.
Size
Optional size sets inline-size and block-size from the space scale (--space-*), mirroring inline SVG sizing on Image. Each axis is a SpaceOption step (0, 4, 8, … 128). Omit an axis to keep the intrinsic aspect ratio from viewBox.
Stroke width and alignment
strokeWidth is a ShapeStrokeWidth enum value in viewBox user units, not screen pixels: Hairline = 1, Thin = 2, Regular = 3, Thick = 4. Alignment math runs in user space; the component does not use vector-effect.
The root svg is overflow: visible, so an Outside stroke or a Center stroke on a path that touches the viewBox edge is not cut off. strokeAlignment defaults to Center. Inside and Outside are meaningful only for closed paths; open paths should use Center.
Alignment
What is drawn
Center
One path with fill and stroke centered on the path centerline at strokeWidth.
Inside
Fill on the path; then a clip path matching the shape (clipPathUnits="userSpaceOnUse") and a stroke at 2×strokeWidth clipped to the interior.
Outside
Fill on the path; then a mask (maskUnits="userSpaceOnUse") with a padded white rect and a black shape cutout, and a stroke at 2×strokeWidth visible only outside the fill.
Clip and mask ids are generated with useId and stay internal.
Paint
fill and stroke accept VectorPaint:
Tokens:Foreground values used for Text and Icon paint, except Foreground.Inherit.
ChartColor: palette CSS variables for series you control.
DataColor: pass-through colors from third-party data; see Data color. Prefer tokens or ChartColor for anything Axon 911 owns; use DataColor only when the payload cannot be mapped (for example ESRI layer styles).
Omitted fill or stroke paints none for that side.
Meaningful vs decorative
Same union as Image inline SVG:
Meaningful:label (LocalizedMessage) → role="img" and aria-label on the root svg.
Decorative:decorative: true → aria-hidden="true" and focusable="false".