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 |
[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 handlerDescription#
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: reduceset 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#teaminto 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.ScrollToleaves 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 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?#
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
foreachcannot be scrolled to byNavigation.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 header covers the top of the section — how do I clear it?#
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:andScrollBehavior - [[ui-animation#reduced-motion]] — the rest of what the platform does when a reader asks for less motion