Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

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

Stack(id: services) { … } · Link(to: services) { … } · Navigation.ScrollTo(services)

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.

stable3 examples compiled by CIuinavigationaccessibilitylayout

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 writewhat it iswhen
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 codeafter an action — "Book" scrolls to the booking form
[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#

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#

What does going to a section do?#

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?#

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

you wrotethe 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?#

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?#

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 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:

[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?#

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#

A "back to top" button that is not a link#

[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#

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

Related

Navigation

The routes the user has open, which of them hold UNSAVED edits, and the verbs that move between them or close one…

accessibility

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

style props

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

animation — keyframe motion, on entry, on exit, in a loop, or as it scrolls into view

An `animation` block declares reusable keyframe motion — an entrance, a shimmer, a pulse, a flash that decays. Its…