---
title: Props and children
description: How a prop stays current while the child runs once, how a callback prop talks back, and where children go.
sidebar: { label: Props and children }
---

Last page, a list repeated markup. Repeated markup often becomes its own component, and components talk through props.

Pass `count` to a child, and the child stays current. Yet the child body still runs only once.

A component's parameter is its props. A prop reads the parent's current value, not a copy. When `count` changes, Markless updates the text inside the child, and the child body stays put.

Click the number button in the page.

<CompLivePropsFigure />

Notice that **Times Badge ran** stays at 1.

## Pass a prop

```tsrx Counter.tsrx
import { state } from '@markless/core';

function Badge({ value }: { value: number }) @{
	<strong class="badge">{value}</strong>
}

export default function Counter() @{
	let count = state(0);

	<button onClick={() => count++}>
		<Badge value={count} />
	</button>
}
```

For this file, a click plans one update: the text inside `<strong>`. Nothing calls `Badge` again.

## Talk back with a callback

```tsrx Page.tsrx
import { state } from '@markless/core';

function Picker({ onPick }: { onPick: (name: string) => void }) @{
	<button onClick={() => onPick('Ada')}>Pick Ada</button>
}

export default function Page() @{
	let picked = state('nobody');

	<section>
		<p>Picked {picked}</p>
		<Picker onPick={(name) => (picked = name)} />
	</section>
}
```

A callback prop takes zero or one parameter. Two or more fail with `MARKLESS_CALLBACK_PROP_ARITY_UNSUPPORTED`. To send more, pass one object.

## Children

Markup between a component's tags arrives as `children`. Place it with `{children}`. Type it with `Children` from `@markless/core`.

```tsrx Card.tsrx
import type { Children } from '@markless/core';

export function Card({ title, children }: { title: string; children?: Children }) @{
	<section class="card">
		<h2>{title}</h2>
		{children}
	</section>
}
```

:::warning[Why can't I map over children?]
You can't look inside `children`. You can place it, wrap it, or pass it on. If you map, count, or index it, the build fails with `MARKLESS_CHILDREN_OPAQUE`.
:::

:::warning[Why is my prop default refused?]
A default such as `{ text = 'none' }`, read in markup, fails with `MARKLESS_STATE_DESTRUCTURE_DEFAULT_UNSUPPORTED`. Drop the default. Write `const shown = computed(() => text ?? 'none')` and show `{shown}`.
:::

**Coming from another framework?**

In React, a new prop value calls the child function again. Here the child function runs once. Markless writes the new value into the one spot that shows it.

**Next:** How do you style `Badge` without the CSS leaking into other files? [Styles →](/components/styles)
