/ component

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.

/ basic-usage

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 center
/ placement-grid

The 3×3 placement grid, snapped live

anchor mode

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.

previewnot supported

CSS anchor positioning is unavailable here — showing a static diagram. The produced value still copies and works in a supporting browser.

top center
produced value
position-area: top center

The 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.

/ types

Three usage tiers

From useState-and-go to compile-time cross-axis-grammar validation.

01 casual
string

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 center
const [value, setValue] = useState<string>("span-all center")
02 intellisense
PositionAreaString

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-start
const [value, setValue] = useState<PositionAreaString>("block-start inline-start")
03 strict
PositionAreaLiteral<S>

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 left
cssPositionArea("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

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.

PropTypeDescription
valueAnchorPositionString | (string & {})Current anchor-positioning value string. Required. An empty / unparseable value seeds an empty editor for the mode.
onChange(next: AnchorPositionString) => voidFires 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); the anchor()/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-end keywords (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-try at-rule, position-try-order, and position-visibility are out of strict scope — the component edits the three value grammars in isolation.
/ install

Drop it in

One command via the shadcn CLI.

$ pnpm dlx shadcn@latest add https://ridiculous.turtlesocks.dev/r/anchor-position-editor.json