# Views (/docs/products/dash-dots/developers/views)

A page's view lives in `view.dml`. It looks like HTML, built from Dot's [components](/docs/products/dash-dots/developers/components), and shows the page's state.

```xml title="pages/index/view.dml"
<View>
  <Text variant="title">Hello, {{ name }}</Text>
  <View variant="card" direction="row" justify="between" align="center">
    <Text>{{ count }} {{ count === 1 ? 'tap' : 'taps' }}</Text>
    <Button onClick="increment" disabled="{{ count >= 5 }}">Add one</Button>
  </View>
</View>
```

The build checks every name in it and points at the line when something's off.

## One root View

Wrap the whole page in one `<View>`. It fills the screen with roomy padding. For an edge-to-edge banner, use `<View padding="none">`.

Text goes inside `<Text>`. A few components, like `Button` and `Badge`, take their text directly: `<Button>Pay</Button>`.

## Bindings

A binding reads the page's state inside double braces. When the state changes, the view follows.

```xml
<Text>{{ paid ? 'Paid' : 'Due' }}</Text>
<Text>SLE {{ price * quantity }}</Text>
<Button disabled="{{ !agreed || sending }}">Send</Button>
```

When a binding is the whole attribute, the value keeps its type, so `disabled="{{ count >= 5 }}"` is a boolean. Mixed with other text, it becomes a string: `title="Meter {{ meter }}"`.

Bindings can't call functions. Format dates and the like in the controller, and keep the result in state.

A true-or-false prop on its own means true: `<Text numeric>`.

## Events

Event attributes start with `on`. Their value is the name of a handler in the controller:

```xml
<Button onClick="save">Save</Button>
<Input label="Meter number" value="{{ meter }}" onChange="setMeter" />
```

The handler is a mutation or an action (see [Pages and state](/docs/products/dash-dots/developers/pages-and-state)). Its first argument is the page's state, and its second is the event. `event.detail` holds what the component reports:

```ts
setMeter: (_state, event: ValueEvent) => ({ meter: event.detail.value }),
```

These event types work in every page without an import:

| Type              | Use it for                                                                             |
| ----------------- | -------------------------------------------------------------------------------------- |
| `ValueEvent`      | A field's text in `detail.value`. Use `ValueEvent<number>` for a `Slider` or `Stepper` |
| `CheckedEvent`    | `detail.checked`, from a `Switch` or `Checkbox`                                        |
| `IndexEvent`      | `detail.index`, from a `Carousel` or `Scroll`                                          |
| `DataEvent<Data>` | Any event, with its [data attributes](#data-attributes)                                |

Each component's [reference page](/docs/products/dash-dots/reference/components) lists its events.

## Data attributes

When twenty rows all run `open`, the handler must know which was tapped. Add `data-*` attributes, and it gets them in `event.data` in camelCase, so `data-account-id` arrives as `accountId`:

```xml
<List>
  <For each="{{ accounts }}" item="account" key="id">
    <ListItem title="{{ account.name }}" data-account-id="{{ account.id }}" onTap="open" />
  </For>
</List>
```

```ts
open: (_state, event: DataEvent<{ accountId: string }>) => ({
  selected: event.data.accountId,
}),
```

## Conditions

`<If>`, `<ElseIf>` and `<Else>` show content only when a condition holds:

```xml
<If condition="{{ status === 'paid' }}">
  <Notice tone="success" title="Paid">Your token is on its way.</Notice>
</If>
<Else>
  <Spinner />
</Else>
```

`<ElseIf>` and `<Else>` go right after an `<If>` or `<ElseIf>`. None of them take events, so put handlers on what's inside.

## Hide or lock a View

An `<If>` removes what's inside, so it starts afresh when it comes back. To keep it alive, set `hidden` on a `View`. What's typed stays, and so does a `Scroll`'s position:

```xml
<View hidden="{{ tab !== 'details' }}" gap="medium">
  <Input label="Your name" value="{{ name }}" onChange="setName" />
</View>
```

While a `View` is hidden, a `Sheet` inside it stays shut. Use an `<If>` when the content should start afresh, or isn't needed until it shows.

`disabled` dims a `View`, and nothing in it takes a tap. Screen readers skip it, so say why outside it:

```xml
<Text>The waiting list opens on Friday.</Text>
<View disabled="{{ !listOpen }}" variant="card" gap="medium">
  <Input label="Your phone number" placeholder="+23276123456" />
  <Button>Join the waiting list</Button>
</View>
```

`ListItem`, `GridItem`, `Link`, `Tab`, `Disclosure` and `Option` take `disabled` too. A tap on something disabled stops there, so it doesn't reach a tappable `View` around it.

## Lists

`<For>` repeats its content for each item in an array:

```xml
<For each="{{ orders }}" item="order" index="i" key="id">
  <Text>{{ i + 1 }}. {{ order.title }}</Text>
</For>
```

`item` and `index` name each item and its position. `key` names a property that's unique to each item. For a list of plain strings or numbers, write `key="*this"` to use the item itself.

> **Tip:** Always set a `key`. It keeps each row, and whatever was typed in it, with the right item when the list changes.

## Layout and styling

There's no CSS. You lay things out with a `View`'s props, like the card at the top of this page, and Dash picks sizes and colors to match the app.

- Children stack in a column. `direction="row"` puts them side by side.
- `gap` and `padding` take `none`, `small`, `medium` or `large`. `paddingTop`, `paddingBottom`, `paddingStart` and `paddingEnd` pad one side. Start and end follow the reading direction.
- `justify` places children along the direction, and `align` across it.
- `wrap` lets a row of chips flow onto a new line. `grow` takes the room left over.
- `variant="card"` draws a card.

See [`View`](/docs/products/dash-dots/reference/components/view) for every prop.

### Style props

For more than a card, a `View` takes style props that follow the [theme](/docs/products/dash-dots/developers/theming):

- `background`: `background`, `surface`, `primary`, `muted`, a soft tone (`info`, `success`, `warning`, `danger`), or one of your [custom colors](/docs/products/dash-dots/developers/theming#custom-colors) or [gradients](/docs/products/dash-dots/developers/theming#gradients).
- `border`: `none`, `subtle` or `strong`.
- `radius`: `none`, `small`, `medium`, `large`, or `full` for pills and circles.
- `elevation`: a shadow under the box, `none`, `low` or `high`. Use `high` for what floats over everything else.
- `width` and `height` in points, or `fill`.
- `aspect` sets the height from the width, with a ratio like `16:9`.

```xml
<View background="primary" radius="large" padding="large" gap="small">
  <Text variant="caption" tone="onPrimary">Your balance</Text>
  <Text variant="display" tone="onPrimary" numeric>{{ balance }}</Text>
</View>
```

On `primary`, give text `tone="onPrimary"` so it stays readable.

### Layers with Stack

`<Stack>` piles its children up, first at the bottom. Each child covers the whole stack, so place its content with that child's `justify`, `align` and padding.

```xml
<Stack radius="large" aspect="16:9">
  <View background="gold" />
  <View justify="end" padding="large">
    <Text variant="subtitle" tone="ink">Bo market day</Text>
    <Text tone="ink">Every Saturday, from 7 am</Text>
  </View>
  <View align="end" padding="medium">
    <View background="primary" radius="full" paddingStart="small" paddingEnd="small">
      <Text variant="caption" tone="onPrimary" weight="bold">New</Text>
    </View>
  </View>
</Stack>
```

![A gold card with Bo market day at the bottom and a New badge in the corner](/assets/images/dash-dots/components/stack.webp)

`gold` and `ink` are [custom colors](/docs/products/dash-dots/developers/theming#custom-colors). For text over a photo, put an `Image` first.

## Make a picture of a View

Give a `View` an `id`, and `this.captureView()` makes a PNG of it, such as a ticket to share. It draws the whole `View`, even the part scrolled out of sight.

```xml
<View id="ticket" variant="card" gap="small">
  <Text variant="title">Lungi ferry</Text>
  <Text>Mariama Kamara, Saturday at 9:00 AM</Text>
  <QRCode value="{{ ticketCode }}" size="160" />
</View>
<Button onClick="shareTicket">Share ticket</Button>
```

```ts title="controller.ts"
// One of the page's actions
async shareTicket() {
  const picture = await this.captureView('ticket');
  await this.dash.system.share({ file: picture });
}
```

The picture is a [file object](/docs/products/dash-dots/developers/network-and-files#file-objects), so you can show, share, upload or save it.

- An `id` takes letters, numbers, `-` and `_`. Give each `View` on a page its own.
- A new picture of a `View` replaces the last one, and older file objects of that `View` show the new picture. [Save](/docs/products/dash-dots/developers/network-and-files#the-dots-own-files) a picture to keep it.
- An `Image` in the `View` must load within 15 seconds. If it comes from another site, that site's server must allow CORS.

`captureView` rejects if the page has no such `View`, if the `View` isn't showing or is too big, or if an `Image` won't load. The error says what to fix.
