# ConditionalRecipe

Add scoped attributes for variant selections that apply only inside a named condition.

The compiler rewrites a [`variants`](/docs/api/core/variants) definition with `conditions` into a `ConditionalRecipe.create` call. Applications normally do not import it. Each conditional selection becomes one attribute that compiled rules match inside the condition's query, so the helper never evaluates a query.

```ts title="button.ts"
import { ConditionalRecipe } from 'zyzz/runtime'

const button = ConditionalRecipe.create({
  axes: { size: ['small', 'large'] },
  className: 'z-button',
  // Index 0 in attribute names refers to `wide`
  conditions: ['wide'],
  defaults: { size: 'small' },
})

// { className: 'z-button', 'data-size': 'small', 'data-zyzz-condition-0-size': 'slarge' }
export const props = button({ conditions: { wide: { size: 'large' } } })
```

## ConditionalRecipe.create

```ts
// Recipe metadata plus the ordered condition names
ConditionalRecipe.create(options)
```

### options.axes

* **Type:** `{ [axis: string]: readonly string[] }`

Each axis and its choices, in authored order. Every axis may also take a conditional selection.

```ts
// Conditional attributes exist for size only
ConditionalRecipe.create({
  axes: { size: ['small', 'large'] },
  className: 'z-button',
  conditions: ['wide'],
  defaults: {},
})
```

### options.className

* **Type:** `string`

The complete generated class list, including the class of every conditional rule.

```ts
// Conditional selections never add a class
ConditionalRecipe.create({
  axes: {},
  className: 'z-button',
  conditions: ['wide'],
  defaults: {},
})
```

### options.defaults

* **Type:** `{ [axis: string]: string | null }`

The unconditional choice used when an axis is omitted or `undefined`. Defaults never apply inside a condition, so an omitted conditional selection adds no attribute.

```ts
// Adds 'data-size': 'small' but no conditional attribute
ConditionalRecipe.create({
  axes: { size: ['small', 'large'] },
  className: 'z-button',
  conditions: ['wide'],
  defaults: { size: 'small' },
})()
```

### options.conditions

* **Type:** `readonly string[]`

The condition names in authored order. [`Recipe.create`](/docs/api/runtime/namespaces/Recipe#parameters) selects the unconditional attributes from the other options.

```ts
// Attributes use index 0 for wide and 1 for print
ConditionalRecipe.create({
  axes: {},
  className: 'z-card',
  conditions: ['wide', 'print'],
  defaults: {},
})
```

### options.html

* **Type:** `boolean`
* **Default:** `false`

Return HTML attributes. Serialization happens once, after every conditional attribute is added.

```ts
// Returns `class` instead of `className`
ConditionalRecipe.create({
  axes: {},
  className: 'z-card',
  conditions: ['wide'],
  defaults: {},
  html: true,
})
```

### input\[axis]

* **Type:** `string | boolean | null | undefined`
* **Default:** `options.defaults[axis]`

The unconditional choice, serialized as [`Recipe.create`](/docs/api/runtime/namespaces/Recipe#optionsaxis) serializes it. `null` clears the axis outside every condition.

```ts
// { className: 'z-button', 'data-size': 'large' }
button({ size: 'large' })
```

### input.className

* **Type:** `string`
* **Default:** `undefined`

Classes appended after the generated class list.

```ts
// 'z-button wide'
button({ className: 'wide' }).className
```

### input.vars

* **Type:** ``{ [variable: `--${string}`]: string | number | undefined }``
* **Default:** `undefined`

Custom-property assignments merged into a new `style` before `input.style`.

```ts
// { '--accent': 'crimson' }
button({ vars: { '--accent': 'crimson' } }).style
```

### input.style

* **Type:** `style.Options['style']`
* **Default:** `undefined`

Inline overrides, copied after `input.vars`.

```ts
// { padding: '24px' }
button({ size: 'large', style: { padding: '24px' } }).style
```

### input.conditions

* **Type:** `{ [condition: string]: { [axis: string]: string | boolean | null | undefined } | undefined }`
* **Default:** `undefined`

Selections that apply inside each named condition. A choice serializes with an `s` prefix, and `null` serializes as `n`, which clears the axis inside the condition. Omitted and `undefined` conditions or entries add no attribute.

```ts
// Adds 'data-zyzz-condition-0-size': 'n'
button({ size: 'large', conditions: { wide: { size: null } } })
```

The declared return type is React props or `Html.Attributes`, whatever `options.html` is. Generated code casts the callable to `variants.ReturnType`, so direct TypeScript callers narrow with `'className' in props` before reading `className`.

### className

* **Type:** `string`

The generated class list, followed by `input.className`. HTML output has `class` instead.

```ts
// 'z-button'
props.className
```

### class

* **Type:** `string`

The same class list when `options.html` is `true`, which returns no `className`.

```ts
// { class: 'z-button' }
ConditionalRecipe.create({
  axes: {},
  className: 'z-button',
  conditions: ['wide'],
  defaults: {},
  html: true,
})()
```

### data-\[axis]

* **Type:** `string | undefined`

The unconditional choice of each selected axis. A cleared or unselected axis has no attribute.

```ts
// 'small'
props['data-size']
```

### style

* **Type:** `Readonly<Record<string, string | number | undefined>> | string | undefined`

The merged inline styles, present only when the call supplies overrides. HTML output serializes it to a string.

```ts
// { '--accent': 'crimson', padding: '24px' }
button({ style: { padding: '24px' }, vars: { '--accent': 'crimson' } }).style
```

### data-zyzz-condition-\[index]-\[axis]

* **Type:** `string | undefined`

One returned attribute per conditional selection, beside the unconditional `data-[axis]` attributes.

```ts
// 'slarge'
props['data-zyzz-condition-0-size']
```

## ConditionalRecipe.attribute

```ts
// The attribute name shared by compiled rules and the callable
ConditionalRecipe.attribute(options)
```

### options.axis

* **Type:** `string`

The declared axis name.

```ts
// 'data-zyzz-condition-0-size'
ConditionalRecipe.attribute({ axis: 'size', condition: 0 })
```

### options.condition

* **Type:** `number`

The condition's index in authored order.

```ts
// 'data-zyzz-condition-1-size'
ConditionalRecipe.attribute({ axis: 'size', condition: 1 })
```

## Types

* **`ConditionalRecipe.attribute.Options`:** The `{ axis, condition }` input.
* **`ConditionalRecipe.create.Options`:** [`Recipe.create.Options`](/docs/api/runtime/namespaces/Recipe#types) with `conditions`.

```ts title="options.ts"
import type { ConditionalRecipe } from 'zyzz/runtime'

// A recipe whose size can change inside `wide`
export const options = {
  axes: { size: ['small', 'large'] },
  className: 'z-button',
  conditions: ['wide'],
  defaults: { size: 'small' },
} satisfies ConditionalRecipe.create.Options
```

## Errors

Neither function validates selections. The callable's own type accepts any record, so an unknown choice serializes into a conditional attribute as given, and a value that cannot convert to a string, such as a `Symbol`, throws a `TypeError`.

Generated code casts the callable to `variants.ReturnType`, which is how TypeScript rejects unknown conditions, axes, and choices.
