# SelectionTile implementation plan

## Status

Planning only. This document records the agreed component direction; no implementation exists yet.

## Goal

Add a reusable `SelectionTile` for selecting a rich option with either a native radio button or checkbox. The tile may contain arbitrary content, including optional predefined slots for a label, price, and message. Expanders belong in the content slot when order matters.

The component must be vanilla-JavaScript-first. The React API is a convenience that emits the same accessible HTML contract available to non-React consumers.

## Agreed decisions

- Name: `SelectionTile`.
- Support both radio and checkbox controls.
- Reuse the existing Radio/Checkbox, Label, Message, Expander, and BlockAction behavior.
- The content is composable: predefined `label`, `price`, and `message` slots are optional, and `children` accepts arbitrary content in caller-defined order.
- Activating an Expander or another explicit nested action must not select the tile.
- Support warning and error presentation. An error is an invalid form state; a warning is advisory and must not automatically be exposed as `aria-invalid`.
- Do not introduce a SelectionTile JavaScript module unless native input behavior and the existing BlockAction module prove insufficient.

## Proposed anatomy

1. `SelectionTile` root
   - Provides layout, border, theme, and state styling.
   - Reuses BlockAction root positioning and override boundaries.
2. Selection control (required)
   - Selected with `type="radio"` or `type="checkbox"`.
   - Renders the existing Radio or Checkbox visual control internally.
   - Owns the input `id`, name/value, native states, and accessible relationships.
3. Label slot (optional visually, required accessibly)
   - Associated with the input through `for`/`id`.
   - Accepts arbitrary phrasing and inline markup.
   - When omitted, the input must receive an accessible name through `aria-label` or `aria-labelledby`.
4. Optional predefined slots and content slot
   - `price` and `message` are optional convenience slots; `children` controls custom content order.
   - `children` accepts arbitrary non-interactive or explicitly overridden content.
   - SelectionTile must not define product-specific fields.
5. Override/action slot (optional)
   - Uses the existing BlockAction override layer.
   - Intended for Expander and, if needed, other explicitly supported nested interactive content.

## Proposed React contract

Keep the API composition-oriented while letting SelectionTile own the control semantics. Exact native-input prop forwarding should be confirmed while implementing against the existing component types.

```tsx
<SelectionTile
  type="radio"
  id="delivery-standard"
  name="delivery"
  value="standard"
  label={<>Standard delivery</>}
  status="warning"
  price="Free"
>
  <Message id="delivery-standard-message" type="warning">
    Delivery may take longer in remote areas.
  </Message>
</SelectionTile>
```

Initial public surface:

- `type`: required `"radio" | "checkbox"`; selects the existing control primitive.
- `id`: required stable HTML id used by the Label and description relationships.
- `name`: native input name; required for radio groups and optional for checkboxes (Checkbox currently falls back to `id`).
- `value`: native input value, required when the surrounding form needs to distinguish radio/checkbox values.
- `label`: optional `ReactNode`; when omitted, provide `aria-label` or `aria-labelledby` through `inputProps`.
- `price`: optional `ReactNode` price or secondary value slot.
- `message`: optional `ReactNode` feedback slot, typically a Message.
- `children`: arbitrary content rendered in the tile body.
- `status`: optional `"warning" | "error"` visual/semantic state.
- `className`: optional additional root class.
- Native control states and event attributes supported by the existing Radio/Checkbox primitives, including checked, disabled, required, and change/click handlers.
- Standard root HTML attributes that do not belong on the input.

Use the predefined slots when their fixed position fits the anatomy; use `children` when content order matters. Nested interactive content in `children` must use the BlockAction override layer.

## Vanilla HTML contract

Document the exact class-based equivalent of the React output. The final markup should follow this shape, adjusted to the existing Radio/Checkbox sibling requirements:

```html
<div class="selection-tile block-action">
  <div class="selection-tile__control form-field__control">
    <input
      class="radio radiocheck--fill"
      type="radio"
      id="delivery-standard"
      name="delivery"
      value="standard"
      aria-describedby="delivery-standard-message"
    />
    <div class="radio-display" aria-hidden="true"></div>
  </div>

  <div class="selection-tile__body">
    <label class="label" for="delivery-standard">Standard delivery</label>
    <p id="delivery-standard-message" class="message">Supporting content</p>

    <div class="block-action__override">
      <details class="expander" data-expander>
        <summary>Detail</summary>
        <div>Arbitrary detail content</div>
      </details>
    </div>
  </div>
</div>
```

The `.radiocheck--fill` input is the full-tile native target. The documentation example must use the complete current Expander markup, including its required summary classes and icon markup, inside a BlockAction override.

## Interaction and accessibility invariants

- The native input remains the only selection control. Do not give the tile `role="button"`, `role="radio"`, or an additional `tabindex`.
- The input has an accessible name through its associated Label, or through `aria-label`/`aria-labelledby` when the visual label slot is omitted.
- Supporting Hint and Message elements use stable IDs referenced by `aria-describedby` when they help explain the choice or its state.
- Radio tiles belonging to one question share a `name` and are grouped in a `<fieldset>` with a meaningful `<legend>`.
- Checkbox tiles remain independently selectable unless their surrounding form defines another relationship.
- Space changes the focused input using native behavior. Arrow-key radio navigation remains browser-native.
- Clicking the non-interactive tile surface selects/toggles the input.
- Clicking or keyboard-activating Expander only opens/closes its `<details>` content and does not change the input.
- Expander continues to use native `<details>/<summary>` semantics.
- Nested interactive content is allowed only in the BlockAction override layer. Document this constraint.
- Disabled input prevents tile selection and uses disabled styling without removing explanatory content from assistive technology.
- `status="error"` connects the error message and sets `aria-invalid="true"` on the input.
- `status="warning"` connects the warning message but does not set `aria-invalid` automatically.
- Status must not be communicated by color alone; Message text/icon and control state remain perceivable.
- Focus is visible on the whole tile via `:has(input:focus-visible)` while the browser focus remains on the input.
- Light, dark, high-contrast/forced-colors, zoom, and reflow behavior must remain usable.

## State styling

Use existing design tokens and derive state from native control state wherever possible:

- default: neutral border;
- hover: interactive border treatment;
- focus-visible: clearly visible focus indicator with sufficient contrast;
- selected: `:has(input:checked)` accent border;
- warning: explicit warning treatment;
- error/invalid: explicit error treatment;
- disabled: disabled control and non-interactive cursor/treatment;
- combinations: selected + warning/error and selected + disabled must remain distinguishable.

Do not synchronize selected styling with React state or JavaScript. CSS must follow the actual input state so vanilla, keyboard, form reset, and browser restoration all behave consistently.

## Implementation steps

1. Confirm the smallest SelectionTile DOM structure compatible with Radio/Checkbox sibling styling and BlockAction override layering.
2. Implement the SelectionTile React wrapper with required `type`, `id`, and accessible-name path plus arbitrary children and optional predefined slots.
3. Add SelectionTile SCSS using existing spacing, border, color, and theme tokens.
4. Reuse BlockAction for root positioning and nested interactive override; use the native `.radiocheck--fill` input as the full-tile target. Extend BlockAction only if a demonstrated gap cannot be owned by SelectionTile.
5. Export SelectionTile from its local index and the package component index.
6. Include SelectionTile styles in the main component stylesheet bundle.
7. Add a documentation page and navigation entry with matching React and vanilla examples.
8. Add a changelog entry when the component is implemented, following the repository release convention.

## Verification

### Rendering and API

- Renders the existing Radio and Checkbox primitives from `type`.
- Renders arbitrary label and body content without imposing a content schema.
- Forwards native input behavior and root HTML attributes.
- Emits the documented vanilla-compatible class structure.

### Interaction

- Pointer activation on the tile toggles a checkbox.
- Pointer activation selects the correct radio within a group.
- Keyboard operation follows native radio/checkbox behavior.
- Expander pointer and keyboard activation does not change selection.
- Disabled choices cannot be changed.
- Form reset and externally changed input state update tile styling without SelectionTile JavaScript.

### Accessibility and conformance

- Valid HTML conformance test.
- `vitest-axe` coverage for radio group, checkbox, warning, error, disabled, and Expander examples.
- Assertions for Label association, `aria-describedby`, `aria-invalid`, fieldset/legend guidance, and non-duplicated tab stops.
- Focus-visible and status states verified in both light and dark themes.
- Forced-colors behavior checked manually or through the repository's existing visual test path.

### Focused gates

- SelectionTile unit and conformance tests.
- Existing BlockAction, Radio, Checkbox, Message, and Expander tests.
- Lint/type checking for task-relevant files.
- Component bundle build to prove exports and SCSS inclusion.

## Non-goals

- No custom selection model or keyboard handler.
- No required product, price, description, or message schema; predefined slots remain generic React content.
- No replacement or redesign of Radio, Checkbox, Message, Expander, Card, Tile, or BlockAction.
- No automatic inference of status by inspecting arbitrary children.
- No support for unrestricted nested interactive controls; each allowed action must use the BlockAction override contract.
- No new dependency.

## Open implementation checks

- Verify whether the existing `.radiocheck--fill` input overlay plus `.block-action__override` is sufficient in every supported browser. Prefer this existing path.
- Decide whether the root should reuse the existing Tile or Card component after comparing their DOM and spacing constraints. The public name remains `SelectionTile` either way.
- Confirm the exact warning and error border tokens with design. Their semantics are already defined above.
