---
title: Markup and expressions
description: How text, attributes, and expressions in .tsrx markup turn into updates that the compiler plans ahead.
sidebar: { label: Markup }
---

Last page, `{count}` sat inside a button. What else fits inside the braces?

Most expressions do. The compiler reads each pair of braces at build time and notes which state it reads. When that state changes, Markless updates that one text or attribute.

An attribute can do one more thing. Its value decides if the attribute exists at all.

Click **Count** three times.

<CompAttrFigure />

Notice that `disabled` appears only when `count >= 3` turns true.

## Text and attributes

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

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

	<section class={count > 0 ? 'panel active' : 'panel'}>
		<button disabled={count >= 3} onClick={() => count++}>Count {count}</button>
		<output>{count * 2}</output>
	</section>
}
```

For this file, the compiler plans four updates: `class`, `disabled`, the button text, and the `<output>` text. Nothing else changes when `count` changes.

- `false`, `null`, and `undefined` leave the attribute out. `true` writes it empty, as in `disabled=""`.
- `aria-*` and `data-*` attributes write `true` as the text `"true"`.
- A `<` that cannot start a tag is text. `I <3 this` shows as written.
- `{...attrs}` copies attributes once. Later changes to `attrs` do not reach the page, and the compiler warns with `MARKLESS_SPREAD_STATIC_SNAPSHOT`.

## Component tags

`<Badge />` renders the component `Badge`. A tag with a dot, such as `<checkbox.root>`, must come from an import. If it does not, the build fails with `MARKLESS_COMPONENT_TAG_UNRESOLVED`.

:::warning[Why does my function call fail the build?]
The compiler follows reads, operators, and method calls. It cannot look inside your own function. So `{label(count)}` fails with `MARKLESS_TEMPLATE_EXPRESSION_UNSUPPORTED`. Put the call inside `computed(() => label(count))` and read that value in the markup.
:::

:::warning[Why is my class attribute broken?]
`class` takes a string. An object such as `class={{ active: on }}` writes `class="[object Object]"`, and the compiler flags it with `MARKLESS_ATTRIBUTE_OBJECT_VALUE`. Use a string or a `? :` expression.
:::

**Coming from another framework?**

The braces work like JSX braces. Markless writes `class`, as HTML does. A `class` value is a string, not an object.

**Next:** How do you show a part of the page only some of the time? [Control flow →](/components/control-flow)
