# Heading — a page's outline, h1 to h6

> A heading is part of a page's outline, not only its type: a screen-reader user moves through a page by jumping from heading to heading. `PageTitle` is the page's level-1 heading, `CardTitle` (and so every `Card` and `Dialog` title) a level-2 one, and `Heading(text, level:)` any level from 1 to 6 in the kit's type scale.

<!-- id: ui-headings · area: ui · stability: stable · html: https://osysharp.com/reference/ui/headings/ -->

## Summary        {#summary}

Big bold text looks like a heading and is not one. A reader who cannot see the page asks it for its headings and
jumps between them, the way a sighted reader scans the large type — so a title drawn as
`Text("Orders", fontSize: FontSize.Title)` is a heading to the eye and to nothing else.

The kit's three heading controls say it for you:

| control | heading level | size |
|---|---|---|
| `PageTitle("Orders")` | 1 — one per page | `FontSize.Title` |
| `CardTitle("Billing")` — and every `Card("…")` and `Dialog("…")` title | 2 | `FontSize.Subhead` |
| `Heading("Opening hours", level: n)` | `n`, 1 to 6 (default 2) | follows the level |

```osy title="a page whose outline a screen reader can read" test app=ui-headings
using Osysharp.Ui;

[Route("/")]
[AllowAnonymous]
[Render(CSR)]
[Title("Café Linden")]
component Home() {
  render {
    Stack(gap: 4, p: 6) {
      PageTitle("Café Linden");                 // heading, level 1
      Card("Opening hours") {                   // heading, level 2
        Heading("Weekdays", level: 3);
        Text("7:00 – 18:00");
        Heading("Weekends", level: 3);
        Text("9:00 – 16:00");
      }
    }
  }
}
```

## Signature      {#signature}

```osy syntax
Heading(string text, int level = 2)     // 1–6; outside that range it is drawn and announced as the nearest one
PageTitle(string text)                  // level 1
CardTitle(string text)                  // level 2
Text(text, role: UiRole.Heading, level: 3)   // the same fact on any element of your own
```

## Description    {#description}

### Which level should a heading be?   {#levels}

The level is the heading's place in the page's outline, not its size: level 1 is what the page IS (one per page —
`PageTitle`), level 2 a part of it (a card, a section), level 3 a part of that. A reader hears "heading, level 3,
Weekends" and knows it belongs to the level-2 heading before it. Skipping a level is legal and reads as a gap, so
prefer the next level down.

The size follows the level so that saying which heading it is is enough:

| level | size | weight |
|---|---|---|
| 1 | `FontSize.Title` | the theme's `FontWeight.Headings` |
| 2 | `FontSize.Heading` | the same |
| 3 | `FontSize.Subhead` | the same |
| 4–6 | `FontSize.Body` | the same |

All of them wear the theme's display face (`Font.Display`), like `PageTitle` and `CardTitle`, so a theme that gives
headings a face of their own reaches every one.

### A heading of your own   {#own}

Any element can be one: `role: UiRole.Heading` with `level:` lowers to `role="heading" aria-level="…"`, which every
screen reader treats exactly as an `<h1>`–`<h6>`. Always pass the level — a heading without one is announced as
level 2 whatever it is.

```osy title="a styled heading that is still a heading" syntax
Text("This week", role: UiRole.Heading, level: 2, fontSize: FontSize.Title, color: Colors.Primary);
```

### How do I test it?   {#testing}

A heading is read as text — `Assert.Visible("Opening hours")`. That it is a heading, and at which level, is a fact for
a real browser: the kit's own UX gate checks `PageTitle` is the page's level-1 heading and `Heading(…, level: 3)` a
level-3 one (`osy-client/tests/e2e/visual/suites/ux-fixture-a11ykit.mjs`).

## Examples       {#examples}

```osy title="a list page: the title, then a heading over each group" test app=ui-headings-groups
using Osysharp.Ui;

class Dish { public string Name; public string Course; }

[Route("/menu")]
[AllowAnonymous]
[Render(CSR)]
[Title("Menu")]
component MenuPage() {
  Dish[] dishes = [
    new Dish { Name = "Soup of the day", Course = "Starters" },
    new Dish { Name = "Roast squash", Course = "Mains" },
  ];
  render {
    Stack(gap: 3, p: 6) {
      PageTitle("Menu");
      foreach (var course in dishes.Select(d => d.Course).Distinct()) {
        Heading(course);
        foreach (var d in dishes.Where(x => x.Course == course)) { Text(d.Name); }
      }
    }
  }
}
```

## See also       {#see-also}
- [accessibility](https://osysharp.com/reference/ui/accessibility/) — the whole semantic vocabulary: roles, states, names
- [Osysharp.Ui (the UI kit)](https://osysharp.com/reference/ui/kit/) — every control in the kit
- [style props](https://osysharp.com/reference/ui/styling/) — the type scale the levels are drawn in
