Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

Heading — a page's outline, h1 to h6

Heading(text, level: 2) · PageTitle(text) (level 1) · CardTitle(text) (level 2) · Text(…, role: UiRole.Heading, level: n)

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.

stable2 examples compiled by CIuikitaccessibilitytypography

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:

controlheading levelsize
PageTitle("Orders")1 — one per pageFontSize.Title
CardTitle("Billing") — and every Card("…") and Dialog("…") title2FontSize.Subhead
Heading("Opening hours", level: n)n, 1 to 6 (default 2)follows the level
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#

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#

Which level should a heading be?#

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:

levelsizeweight
1FontSize.Titlethe theme's FontWeight.Headings
2FontSize.Headingthe same
3FontSize.Subheadthe same
4–6FontSize.Bodythe 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#

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.

Text("This week", role: UiRole.Heading, level: 2, fontSize: FontSize.Title, color: Colors.Primary);

How do I test it?#

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#

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#

Related

accessibility

Tags already give an element its role, focus and keyboard behaviour. The semantic props say the rest: `role:` for a…

Osysharp.Ui (the UI kit)

The bundled UI kit — ready-made styled controls like `Button`, the shared design-system vocabularies (`Tone`, `Size`)…

style props

Inside a `variants` block, each `Name = value` is a style prop from a fixed vocabulary the renderer maps to CSS — paint…

Ui — drive the app's UI from a test

Drive the real UI from a test: navigate to a route, click what a person would click, and assert on what the screen…