# variable

Declare a typed CSS variable that styles read and assign, statically or when applied.

A reference works as a declaration value and as a computed key inside `vars`. The compiler names the custom property after the authored binding, so `variables.accent` below becomes `--z-variables-accent`.

```tsx title="Plan.tsx"
import { style, variable } from 'zyzz'

export function Plan(props: Plan.Props) {
  return (
    // An inline assignment overrides the static one on this element
    <section {...styles.plan({ vars: { [variables.accent]: props.accent } })}>
      <h3 {...styles.name()}>Pro</h3>
    </section>
  )
}

export declare namespace Plan {
  type Props = { accent?: string | undefined }
}

namespace variables {
  export const accent = variable('color')
}

namespace styles {
  export const name = style({ color: variables.accent, margin: 0 })

  export const plan = style({
    borderLeft: '4px solid',
    borderLeftColor: variables.accent,
    vars: { [variables.accent]: '#0070f3' },
  })
}
```

## Signature

```ts
// Any string or number
variable()

// Typed, and registered with @property when options are passed
variable(kind, options?)
```

## Parameters

### kind

* **Type:** `'color' | 'length' | 'number' | 'percentage' | 'signedLength' | 'signedPercentage'`
* **Default:** `undefined`

Constrains the declarations a reference can supply and the values `set` accepts. `length` and `percentage` are nonnegative, and the signed kinds allow negative values. Omitting `kind` accepts any string or number.

```ts
import { style, variable } from 'zyzz'

namespace variables {
  export const gap = variable('length')
}

// `gap` accepts lengths, so `color: variables.gap` fails type checking
const grid = style({ display: 'grid', gap: variables.gap })
```

### options.inherits

* **Type:** `boolean`

Whether the registered property inherits through the DOM. Passing options emits an `@property` rule under the same generated name, and requires a `kind`.

```ts
import { variable } from 'zyzz'

const gap = variable('length', {
  inherits: false,
  initialValue: '0px',
})
```

Without options, the property is unregistered and inherits like any custom property.

### options.initialValue

* **Type:** `string | number`

The registered initial value, matching `kind`. It must be computationally independent, so lengths use absolute units and values cannot depend on `currentColor`, `var()`, `env()`, or font-relative units.

```ts
import { variable } from 'zyzz'

const accent = variable('color', {
  inherits: true,
  initialValue: '#0070f3',
})
```

### options.syntax

* **Type:** `'<color>' | '<length>' | '<number>' | '<percentage>'`
* **Default:** Inferred from `kind`

The registered CSS syntax. Signed and unsigned length kinds both emit `<length>`, and percentage kinds emit `<percentage>`.

```ts
import { variable } from 'zyzz'

const offset = variable('signedLength', {
  inherits: false,
  initialValue: '0px',
  syntax: '<length>',
})
```

### options.id

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

A fixed name, which replaces the name derived from the binding. Variables require it when source runs without a compiler transform. Two declarations with the same name fail compilation, so independent declarations need distinct IDs.

```ts
import { variable } from 'zyzz'

// Names the custom property `--z-acme_2d_accent`
const accent = variable('color', { id: 'acme-accent' })
```

## Returns

### reference

* **Type:** `variable.Reference<kind>`

An opaque string usable as a declaration value, inside template expressions, and as a computed key in [`styles.vars`](/docs/api/core/style#stylesvars) or [`options.vars`](/docs/api/core/style#optionsvars).

```ts
// Reads the variable inside a larger value
const card = style({ padding: `calc(${variables.gap} * 2)` })
```

Computed keys lose each variable's value type in TypeScript, so `vars` assignments are not checked against `kind`.

### set

* **Type:** ``(value) => { [variable: `--${string}`]: value }``

Returns a frozen inline assignment that keeps the variable's value type, for use as a `style` override. It performs no runtime validation and creates no CSS.

```ts
// Type-checked against the `length` kind
styles.grid({ style: variables.gap.set('12px') })
```

## Types

* **`variable.Kind`:** The supported value kinds.
* **`variable.Options<kind>`:** Registration options for a kind.
* **`variable.Reference<kind>`:** A declared variable with its `set` method.

```ts title="spacing.ts"
import { variable } from 'zyzz'

// Accepts any length reference, such as a spacing scale entry
export function double(reference: variable.Reference<'length'>) {
  return `calc(${reference} * 2)`
}
```

## Errors

TypeScript rejects values that do not match a variable's kind.

```ts
import { variable } from 'zyzz'

const accent = variable('color')

// A length is not a color
accent.set('12px')
// error: Argument of type '"12px"' is not assignable to parameter of type 'Color'.
```

The compiler reports a variable declared outside a module-level constant as `Source.ExtractError`. Without a compiler transform, a variable without `options.id` throws an `Error`.

## React Native

Native views have no custom properties, so native compilation rejects modules that declare or read variables. Native themes use [`vars`](/docs/api/core/defineConfig/vars) tokens, which resolve to native values that [`useVars`](/docs/api/react-native/useVars) reads in components.
