Anchor Position Editor
Ridiculously typed editor for CSS anchor positioning (mode prop). The strict tier enforces the position-area cross-axis rule — a keyword pair must sit on two different axes of the same coordinate system. The hero is a clickable 3×3 placement grid with a live snap preview.
Place an anchored element
Controlled value + onChange. The default position-area mode shows a clickable 3×3 grid: pick a cell and the editor emits the position-area keyword pair (top center, bottom right, …). The center cell collapses to the single keyword center. Logical / physical and span toggles never mix coordinate systems, so the pair is valid by construction.
position-area: top centerThe 3×3 placement grid, snapped live
The hero of mode "position-area": a clickable 3×3 grid around a mock anchor. Click a cell to emit its position-area keyword pair. A logical / physical toggle swaps the vocabulary (top left ⇄ block-start inline-start) — never mixing the two systems, so the cross-axis rule holds by construction. A span toggle switches the chosen axis keyword to its span- reach form, and the live preview snaps a positioned box to the chosen cell. Switch the mode to build an anchor() expression or a reorderable position-try chain.
position-area: top centerThe value lands on the position-area property (or, for anchor(), any inset property). The preview is gated on CSS.supports("position-area: center") and degrades to a static diagram where anchor positioning is unavailable.
Three usage tiers
From useState-and-go to compile-time cross-axis-grammar validation.
Pass any string
useState<string>. No compile-time validation; the runtime parser tokenizes the position-area pair and classifies every keyword by axis — including exotic span- / self- forms the strict tier treats structurally.
span-all centerconst [value, setValue] = useState<string>("span-all center")
Position-area-shaped hints
State typed as PositionAreaString — a keyword-pair-shaped string (and the onChange return type). An AnchorStringMap keys the output by mode, so the anchor and position-try dialects get their own AnchorString / PositionTryString.
block-start inline-startconst [value, setValue] = useState<PositionAreaString>("block-start inline-start")
Anchor grammar typed at compile time
cssPositionArea() / cssAnchor() / cssPositionTry() validate the cross-axis rule (a pair must sit on different axes of the same coordinate system), the anchor() side / size keyword and its <length-percentage> fallback, and every try-fallback — resolving any violation to never before you run the code.
top leftcssPositionArea("top left") // ✓ // @ts-expect-error both on the y axis cssPositionArea("top bottom") // @ts-expect-error mixes physical + logical cssPositionArea("left block-start") // @ts-expect-error fallback must be <length-percentage> cssAnchor("anchor(top, red)")
Note: the ambiguous start/end keywords (axis depends on writing-mode), <dashed-ident> grammar beyond the -- prefix, and calc()/var() fallbacks are deferred to the runtime parser (lenient by design).
API
Public surface — component props, runtime helpers, and the type exports.
§ AnchorPositionEditor / AnchorPositionEditorPanel
<AnchorPositionEditor
value: AnchorPositionString | (string & {})
onChange: (next: AnchorPositionString) => void
mode?: "position-area" | "anchor" | "position-try" // default "position-area"
className?: string
aria-label?: string
/>AnchorPositionEditor is popover-wrapped (a trigger showing the mode badge + truncated value); AnchorPositionEditorPanel renders the same editor inline. Both are controlled. The mode prop selects the position-area, anchor, or position-try dialect.
| Prop | Type | Description |
|---|---|---|
| value | AnchorPositionString | (string & {}) | Current anchor-positioning value string. Required. An empty / unparseable value seeds an empty editor for the mode. |
| onChange | (next: AnchorPositionString) => void | Fires when the value changes. Emits the canonical value string for the active mode. |
| mode | "position-area" | "anchor" | "position-try" | Dialect. Default "position-area" (the 3×3 grid). "anchor" edits an anchor()/anchor-size() expression; "position-try" edits a reorderable fallback chain. |
§ Sub-components
<PositionAreaGrid system span row col onChange />
The 3×3 placement grid. Cells are labelled <button>s with aria-pressed; a logical/physical toggle swaps the keyword vocabulary and a span toggle switches the chosen axis keyword to its span- reach form. Emits a PositionAreaState.
<AnchorExprFields expr onChange />
anchor mode: a function select (anchor/anchor-size), an optional --name input, a side/size select (options swap with the function), and an optional unit-input <length-percentage> fallback.
<TryFallbackChain fallbacks onChange />
position-try mode: a reorderable list of fallback chips (none / a position-area / a dashed-ident + try-tactic), with up/down/remove buttons (no drag-drop dependency) and an add control.
<AnchorPreview value />
The live mock-anchor + positioned-box preview. Gated on CSS.supports('position-area: center'); degrades to a static diagram with a support note where anchor positioning is unavailable.
<MiniSelect value options onChange />
The local compact <select> chrome. Each component owns its copy (registry self-containment) — it is not imported from query-builder.
<LiveString value />
The produced value rendered in a <code> block.
§ Runtime helpers
cssPositionArea<S>(value: S & PositionAreaLiteral<S>): S
Call-site validator for a position-area value. Mirrors cssMediaQuery() / cssTransform().
cssAnchor<S>(value: S & AnchorLiteral<S>): S
Call-site validator for an anchor() / anchor-size() value.
cssPositionTry<S>(value: S & PositionTryLiteral<S>): S
Call-site validator for a position-try-fallbacks value.
parsePositionArea(src): { keywords; error } · parseAnchor(src): AnchorExpr | null · parsePositionTry(src): TryFallback[]String → editor state. The runtime superset of the strict tier: it tokenizes the structure and surfaces the cross-axis verdict, parsing exotic keywords the strict tier defers.
formatPositionArea(keywords) · formatAnchor(expr) · formatPositionTry(fallbacks)
Canonical re-serialization. A duplicate center pair collapses to the single keyword center.
axisOf(keyword) · areCompatible(a, b)
Runtime mirrors of the type AxisOf / Compatible: a keyword's axis tag (x/y/block/inline/neutral/unknown), and whether two keywords pair (different axes of the same system).
cellToKeywords(row, col) · keywordsToCell(keywords)
The 3×3 grid ⇄ keyword-pair mapping (physical default). keywordsToCell is order-insensitive and maps logical pairs onto the same cells.
positionAreaKeywords() · anchorSides() · anchorSizes() · tryTactics() · defaultFor(mode)
The <select> option sources (the strict whitelists) and a sensible seed value per mode.
§ Types
- PositionAreaLiteral<S> / AnchorLiteral<S> / PositionTryLiteral<S>
- Strict validators — S if S is a valid value for its dialect, else never. The position-area cross-axis rule is the namesake.
- PositionAreaString / AnchorString / PositionTryString / AnchorPositionString
- Suggestion unions (value-shaped strings). AnchorPositionString is the onChange return type.
- AnchorStringMap / AnchorPositionMode
- Mode → output-string map, and the mode discriminant (position-area | anchor | position-try).
- PaKeyword / AnchorSideKeyword / AnchorSizeKeyword / TryTactic
- The position-area keyword union, the anchor() side and anchor-size() dimension keywords, and the three try-tactics (flip-block | flip-inline | flip-start).
- PositionAxis / AxisOf<K> / Compatible<A, B>
- The axis tag union (x|y|block|inline|neutral), the keyword→axis lookup, and the 5×5 cross-axis compatibility check.
- KeywordsOf<S>
- The keyword tuple of a position-area value.
- AnchorExpr / TryFallback / PositionAreaState
- The internal editor state (a parsed anchor() expression; a single try-fallback by kind; the grid state). Exported for advanced use.
§ Strict-tier scope (validated vs deferred)
- Validated: keyword membership; the position-area cross-axis + system-mix rule for physical / logical pairs (
top bottom→never;left block-start→never); theanchor()/anchor-size()function name, side / size keyword, and<length-percentage>fallback dimension; and every try-fallback's well-formedness. - Deferred (lenient → runtime parser): the ambiguous
start/end/self-start/self-endkeywords (tagged neutral — their axis depends on writing-mode);<dashed-ident>validity beyond the--prefix;calc()/var()/env()fallbacks; and order / duplication in the<dashed-ident> || <try-tactic>arm. - The
@position-tryat-rule,position-try-order, andposition-visibilityare out of strict scope — the component edits the three value grammars in isolation.
Drop it in
One command via the shadcn CLI.
$ pnpm dlx shadcn@latest add https://ridiculous.turtlesocks.dev/r/anchor-position-editor.json