> ## Documentation Index
> Fetch the complete documentation index at: https://aerojs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Reactive props

> Share mutable state between parent pages and child components with bind: and Aero.bindable().

## The problem

Component `props` are build-time values. A parent counter and a child header that both edit the same number need either duplicated state, callback props, or an external store.

## What this looks like without Aero

Parents pass initial values; children copy them into local variables. Keeping parent and child in sync means custom events or shared global state.

## How Aero helps

**Reactive props** let a parent pass a state binding into a child with `bind:<name>`. The child declares the prop in `<script is:state>` and marks it mutable with `Aero.bindable()`.

Reactive props are **readonly by default**. Only props declared with `Aero.bindable()` (or `Aero.bindable(fallback)`) accept writes from the child.

## Parent: pass a binding

```html theme={"theme":"github-dark-default"}
<!-- pages/counter.html -->
<script is:state>
	let count = 0
</script>

<header-component bind:count="{ count }" />
<button on:click="{ count++ }">{ count }</button>
```

## Child: accept a bindable reactive prop

```html theme={"theme":"github-dark-default"}
<!-- components/header.html -->
<script is:state>
	const { count = Aero.bindable(0) } = Aero.props
</script>

<div>
	<button on:click="{ count-- }">−</button>
	<span>{ count }</span>
	<button on:click="{ count++ }">+</button>
</div>
```

`Aero.bindable()` marks `count` as writable from the child. Parents must pass bindable props with `bind:count` — one-way `count="{ count }"` is an error. `Aero.bindable(0)` makes the prop optional when the parent omits `bind:count`.

## Required vs optional

Destructuring defaults drive the contract:

| Declaration                | Required? | Bindable?     |
| -------------------------- | --------- | ------------- |
| `count`                    | yes       | no (readonly) |
| `label = 'Counter'`        | no        | no (readonly) |
| `count = Aero.bindable(0)` | no        | yes           |
| `count = Aero.bindable()`  | yes       | yes           |

Required bindable props must be passed with `bind:<name>`. `aero check` and the VS Code extension report missing or one-way bindings.

## Readonly reactive props

Props destructured from `Aero.props` without `bindable()` are readonly in the child state script. Assigning to them is a compile-time diagnostic:

```html theme={"theme":"github-dark-default"}
<script is:state>
	const { label } = Aero.props // readonly — display only
</script>

<p>{ label }</p>
```

Pass readonly reactive props with `label="{ label }"`. To allow the child to mutate a prop, use `Aero.bindable()` on the child and `bind:<name>` on the parent.

## Build-time props vs reactive props

| Mechanism      | When                        | Example                  |
| -------------- | --------------------------- | ------------------------ |
| Build props    | Static data at render time  | `title="{ site.title }"` |
| Reactive props | Shared mutable client state | `bind:count="{ count }"` |

Build scripts still use `Aero.props` for static attributes. State scripts use `Aero.props` with `bindable()` for reactive data.
