# Sections of a page — Link(to:) and Navigation.ScrollTo

> Give an element an `id:` handle and it becomes a section the rest of the page can go to: `Link(to: services)` is an in-page link to it, and `Navigation.ScrollTo(services)` goes there from an action. Both scroll smoothly, jump at once when the reader asks for less motion, and move keyboard focus to the section. The handle is a name the compiler checks, so a mistyped section is an error, never a link that goes nowhere.

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

## Summary        {#summary}

A one-page site — a salon, a studio, a product's landing page — has a nav that jumps to *Services*, *Team* and
*Visit*. In Osy# a section is just an element with an `id:` handle, and two things go to it:

| you write | what it is | when |
|---|---|---|
| `Link(to: team) { Text("Team"); }` | a real in-page link, `<a href="#team">` | a nav, a "back to top", a table of contents |
| `Navigation.ScrollTo(team)` | the same move, from code | after an action — "Book" scrolls to the booking form |

```osy title="a one-page site whose nav goes to its sections" test app=ui-sections
[Route("/")]
[AllowAnonymous]
component Home() {
  action Book() { Navigation.ScrollTo(visit); }
  render {
    Stack(gap: 2) {
      Row(gap: 2) {
        Link(to: services) { Text("Services"); }
        Link(to: team) { Text("Team"); }
        Link(to: visit) { Text("Visit"); }
      }
      Stack(id: services, gap: 1) {
        Text("Services");
        Pressable("Book a visit", onClick: Book);
      }
      Stack(id: team) { Text("Team"); }
      Stack(id: visit) { Text("Visit us"); }
    }
  }
}
```

## Signature      {#signature}

```osy syntax
Stack(id: services) { … }            // ANY element: an `id:` handle makes it a section of this component
Link(to: services) { … }             // an in-page link to it — `<a href="#services">`
Navigation.ScrollTo(services)        // go there from an action or a handler
```

## Description    {#description}

### What does going to a section do?   {#behaviour}

The same thing for both spellings, and what an in-page anchor does on a well-built site:

- **The section scrolls to the top of whatever scrolls it** — the page, or a scrolling container it sits in.
- **It glides, unless the reader asked for less motion.** With `prefers-reduced-motion: reduce` set in their system it
  jumps at once. That is decided at the moment of the click, so changing the setting mid-visit counts.
- **Keyboard focus moves to the section**, so the next Tab continues from there and a screen reader announces where
  the reader landed. An element that cannot normally take focus (a `Stack`) is made focusable for that one visit and
  never becomes a Tab stop.
- **`Link(to:)` writes `#team` into the address** without adding a Back-button entry per section, so a copied address
  or a link opened in a new tab comes back to the same section. `Navigation.ScrollTo` leaves the address alone — it
  is a move made by your code, not a place the reader chose.

### Why is the section a name and not a string?   {#handles}

`id:` takes a bare **handle**, the same one `labelFor:` and `describedBy:` point at ([accessibility](https://osysharp.com/reference/ui/accessibility/)). The
platform mints the real DOM id; you never write one. So every reference is checked when the app compiles:

| you wrote | the compiler says |
|---|---|
| `Link(to: tem)` | `` `to: tem` names no element in this component. Declared here: services, team, visit. `` |
| `Navigation.ScrollTo(viist)` | `` `Navigation.ScrollTo(viist)` names no element in this component. Declared here: … `` |
| `Navigation.ScrollTo("visit")` | it takes a bare NAME, not a string |
| `Link("/team", to: team)` | a section link cannot also take an address |

A raw `Link(href: "#team")` still compiles, and goes nowhere: no element's id is `team`.

### Which elements can a link reach?   {#scope}

The sections of the **same component** — a handle is scoped to the component that declares it, and the link and the
section must both be in its render tree (content you pass into a kit control's slot counts, because it is written in
your tree). Two rules follow from that:

- **A nav in a separate layout component cannot name a page's sections.** Put the section links on the page itself,
  where the sections are.
- **A section inside a `foreach` cannot be scrolled to by `Navigation.ScrollTo`** — every row has its own element, so
  there is no single one to go to. Mark the list's container instead.

### What happens when the section is not on the page?   {#absent}

A section under an `if` that is false is not rendered, so there is nothing to go to: `Navigation.ScrollTo` does
nothing and writes a warning to the log (`osy logs`) naming the handle, and a link's click is left to the browser,
which ignores an unknown fragment.

### A header covers the top of the section — how do I clear it?   {#sticky-header}

A section scrolls to the very top of its scroller, so a sticky header sits over its first line. Give the section a
top scroll margin the height of the header — the same fix as on any web page:

```osy title="sections that clear a 64-pixel sticky header" test app=ui-sections-sticky
[Route("/")]
[AllowAnonymous]
component Home() {
  render {
    Stack {
      Row(position: Position.Sticky, top: 0, h: 64, gap: 2) {
        Link(to: menu) { Text("Menu"); }
        Link(to: hours) { Text("Hours"); }
      }
      Stack(id: menu, scrollMarginTop: 64) { Text("Menu"); }
      Stack(id: hours, scrollMarginTop: 64) { Text("Hours"); }
    }
  }
}
```

### Does a page opened at `#team` start there?   {#arrival}

Yes. When the address names a section of the page — a copied link, a section link opened in a new tab, or
`Navigation.Go("/about#team")` — the page scrolls to it as soon as its data has loaded, instantly (the reader arrived;
they did not ask to watch the page scroll). A fragment that names no section is left alone, so `Navigation.Hash` can
still carry a value of its own ([[ui-navigation#hash]]).

## Examples       {#examples}

### A "back to top" button that is not a link   {#example-back-to-top}

```osy title="a handler that goes back to the first section" test app=ui-sections-top
[Route("/")]
[AllowAnonymous]
component Article() {
  render {
    Stack(gap: 2) {
      Stack(id: top) { Text("A long article"); }
      Stack(h: 2000) { Text("…"); }
      Pressable("Back to top", onClick: () => Navigation.ScrollTo(top));
    }
  }
}
```

## See also       {#see-also}

- [Navigation](https://osysharp.com/reference/ui/navigation/) — moving between PAGES (`Navigation.Go`), and reading the fragment (`Navigation.Hash`)
- [accessibility](https://osysharp.com/reference/ui/accessibility/) — the other things an `id:` handle is for: naming and describing controls
- [style props](https://osysharp.com/reference/ui/styling/) — `scrollMarginTop:` and `ScrollBehavior`
- [[ui-animation#reduced-motion]] — the rest of what the platform does when a reader asks for less motion
