Skip to the content

Surf

Surface component reference, v0

The attributes, the bindings and the events of each built-in component.This page as Markdown
Statusdraft
Editionnone yet. Version 0 has no stability guarantee.
Related documentsLanguage spec v0 (sections 9.2 and 9.6), Surface Layout Spec v0, GLOSSARY.md
This document gives the attributes, the bindings and the events of each built-in component. The compiler checks each attribute and each event on a built-in component against this document. An attribute or an event that is not in this document is a compile error. The checker takes the same table from crates/surf-check/src/builtins.rs.

1. Attributes of all components

Each built-in component takes these attributes in addition to the attributes in its own table:
  • style: the utilities of the component (layout spec, section 12), for example style="p-2 gap-1". A computed value of a utility must be an Int, a Float or a String.
  • id (String): the name of the element for the relations of a screen reader (controls, labelledBy, describedBy, activeDescendant) and for Focus.to. It is also the hook of an extern module: on the web, the element has the attribute id, so the web file of the module finds it with document.getElementById (ADR-0160). A fixed id must be unique (surf lint, rule duplicate-id).
  • role (a role in quotes), level, datetime, expanded, pressed, selected, checked, decorative, focusable, current, controls, activeDescendant, autocomplete, keyShortcuts, labelledBy, describedBy, invalid, hasPopup, orientation, valueNow, valueMin, valueMax, valueText: the meaning of the component for a screen reader (ADR-0051, ADR-0102, ADR-0110). controls, labelledBy, describedBy and activeDescendant name another element by its id. focusable={false} leaves a control out of the order of the Tab key. focusable={true} on an element that is not a control makes it a stop of the Tab key on all targets, and it gets the keys (ADR-0131). A tabpanel gets the focus in the same way. A Scroll with the focus scrolls with the keys of a browser: the arrow keys, Page Up, Page Down, Home, End and the space bar. invalid (Bool?) marks a field with an error: aria-invalid on the web, and the invalid state of AccessKit on the native targets. On the native targets, the text of the element of describedBy is also the description of the control, because not each adapter of AccessKit reads the relation.
  • alt (String): the name of the component for a screen reader. It replaces the text of the component, for example <Button alt="Delete {todo.title}">×</Button>. A container with alt is a group with this name. On the web, alt is the alt of an Image and the aria-label of each other component.
  • title (String): a text for a pointer that rests on the component, and its description for a screen reader (ADR-0032). On the web, it is the title attribute. On the native targets, a small label shows after 0.5 seconds.
  • lang (String): the language of the text of the component and of its children, as a tag such as ja or zh-Hans (ADR-0066). A Han character has different forms in Chinese and Japanese. On the web, it is the lang attribute. On the native targets, it chooses the font and the forms of the shaper. The first 8 bytes of a tag count, as whole subtags. Without it, the platform uses the languages of the user.
  • mask (Icon): limits the component and its children to the shape of an icon (ADR-0065). The shape is the largest that fits into the box, in its middle. The alpha of the icon counts, as in mask-image of CSS: a shape with an opacity of 0.4 shows 40 % of the element (ADR-0131). The hit area stays the box. The shapes of a Drawing do not take it.
  • testid: a name for tests, in quotes, without values. It has no effect on the app. On the web, it is the attribute data-testid. The test driver of the desktop runtime finds elements by it.
A style property is not an attribute. gap={12} is a compile error, and its help text gives the utility style="gap-3".

2. Props

A prop of a built-in component has one of these types:
Type
Values
String
Any text. A string attribute can insert values: placeholder="New {kind}".
Bool
true or false. The attribute name alone means true: disabled.
Float
A Float value. An Int is a compile error, as everywhere in the language: write min={0.0}.
List<Float>
The points of a Polyline or a Polygon: x and y, one after the other.
Icon
An icon: a constant from an .svg import, or a value of Icon.parse (section 4.12).
A prop without a value has the default that the table gives. A bindable prop can take bind: (language spec, section 9.2). Each component has at most one bindable prop, except Video and Audio, which have five (section 4.5b, ADR-0156).

3. Events

An event handler is a function name, statements, or a lambda whose body is statements (language spec, section 9.2). Some events have a value. A handler can take the value as a parameter or ignore it:
surf
<Input on:keydown={key => { if key == "Escape" { editing = false; }}} /><Input on:submit={add} /><Button on:press={count += 1}>Add</Button>
Event
Value
When
press
none
The user activates a Button: a click, a tap, or Enter or Space while the button has the focus.
doubleclick
none
Two clicks in a short time on the component. All built-in components have it, shapes too. A Button also gets two press events before the doubleclick.
focus
none
The control gets the keyboard focus.
blur
none
The control loses the keyboard focus.
key
press: KeyPress
As keydown, with key, shift, ctrl, alt and meta (ADR-0052). on key(press, typing) in a component gets each key press of the window (ADR-0075). typing is true while the focus is in an Input, a TextArea, a Select or an Overlay, so that a key of a menu or a dialog does not start a shortcut of the app (ADR-0110).
keydown
key: String
A key goes down while the control has the focus. The value is the key name of the W3C UI Events specification, for example "Enter", "Escape", "ArrowUp" or "a".
input
the new value
Each edit of the value: each change of the text, or each step of a slider while the user drags it.
change
the new value
The user commits a value. For Input: Enter after an edit, or the loss of the focus after an edit. For TextArea: the loss of the focus after an edit. For Toggle: each change. For Slider: the user releases it.
submit
none
Enter in an Input. After an edit, change comes first. Enter does not send submit while an input method composes text.
scroll
position: ScrollPosition
A Scroll or a List scrolled (ADR-0106). The record has x and y, how far the content moved, and maxX and maxY, the largest values, in logical px. The event does not go to a parent.
visible
visible: Bool
A part of the element enters the window (true), or its last part leaves the window (false). A clip of an ancestor counts. Another element on top does not count. All built-in components with a box have it (ADR-0106). An element that starts outside the window sends nothing.
resize
size: BoxSize
The border box of the element has a new size (ADR-0160). The built-in record BoxSize has width and height in logical px, before the transforms, as ResizeObserver with the border box. The size before the first event is 0 × 0, so an element with no area sends nothing at the start. A hidden element has the size 0 × 0. All built-in components with a box have it.

3.1 Pointer events

Every built-in component has the pointer events (P3.2). Their value is the built-in record Pointer:
surf
type Pointer = { x: Float, // the position in the border box of the component, in logical px y: Float, windowX: Float, // the position in the window, in px of the window: for a drag of a component that moves windowY: Float, dx: Float, // the distance of a wheel in logical px: positive to the right and down dy: Float, button: Int, // 0 main, 1 middle, 2 secondary; -1 for a move and a wheel shift: Bool, alt: Bool, ctrl: Bool, meta: Bool, // Command on macOS, the Windows key on Windows}
Event
When
pointerdown
A button goes down on the component. The component captures the pointer: it gets the pointermove and the pointerup events until that button goes up, also when the pointer leaves its box. The press starts no text selection, no native drag and no context menu of the browser.
pointermove
The pointer moves: to the component with the capture, else to the component under the pointer.
pointerup
The button goes up: to the component with the capture, which then ends. A drag always ends with one pointerup: also after a lost capture, a context menu or the loss of the window focus.
wheel
A wheel or a scroll gesture over the component. The component takes it: no Scroll, List or page under it scrolls.
pointerenter
The pointer enters the component or one of its descendants (ADR-0106). The event does not go to a parent, and a parent gets its own event.
pointerleave
The pointer leaves the component and its descendants. A component that left the tree gets no event.
contextmenu
The user asks for a context menu (ADR-0160): a right click, at the press on macOS and Linux and at the release on Windows, as the system does. On macOS, Control and a click of the main button also ask for it, and that click is no press. The menu key and Shift with F10 ask for the menu of the component with the focus on each system: x and y are then the middle of the component, and button is -1. The menu of the browser does not open for a component that listens to the event.
  • The nearest component from the target up that listens to the event gets it, as for the other events. focus, blur, keydown and key work in the same way, so a container gets them from a control in it (ADR-0102). pointerenter and pointerleave go only to their component.
  • A drag of a knob: on:pointerdown keeps the start, on:pointermove changes the value by p.y, and p.shift makes the steps smaller.
  • On the web, a wheel in lines or pages becomes pixels, and a cancel of the browser (pointercancel) is a pointerup.
  • press and doubleclick still come after a click, also on a component with pointer events.
  • A Drawing and its shapes get x and y in the coordinates of the view of the drawing (section 4.11), not in the border box.

3.2 Rules of all events

These rules apply to all events:
  • An element with hidden and its children get no pointer events, are out of the order of the Tab key, and are not in the accessibility tree, as in a browser (ADR-0110). A control with the focus that gets hidden loses the focus after the next style pass, as in a browser. It gets change after an edit, then blur, and no control has the focus. The Tab key goes on from its place (ADR-0122).
  • On the native targets, a control that gets the focus from a key or from Focus.to scrolls into the area of each Scroll and List around it, as in a browser. It has the focus ring of the web target: 2 px of $focus, 1 px outside its box (ADR-0110).
  • A component gets only the events in its table, and the pointer events.
  • A control with disabled={true} gets no events.
  • A binding writes the new value before the on: handlers of the same event run. The bind:value of Input, TextArea and Slider writes on input. The bind:checked of Toggle writes on change. The bindings of Video and Audio write on their events (section 4.5b).
  • A change from the program does not send events. For example, a write to the bound place of an Input does not send input.
  • change of a text control comes once for one edit, on all targets, as in a browser (ADR-0113). Enter in an Input after an edit gives change and then submit. The loss of the focus after that gives no second change. Enter with no edit gives only submit. Enter in a TextArea makes a new line, and it gives input, not change.
  • Enter that ends the composition of an input method gives no change and no submit, as in a browser. The text of the composition stays, and a later Enter gives change and submit.
  • A computed value that gives the same result again writes nothing (ADR-0131). So a text control keeps the text of the user when an unrelated source of the value changes.
  • The runtime does not send an event to a component while a batch changes the tree. For example, the removal of a control that has the focus does not send blur.

4. Components

4.1 Containers: Row, Column, Stack, Grid

Attribute
Type
Default
(none)
Events: doubleclick, and focus, blur, keydown and key of the controls in the container (ADR-0102).

4.2 Scroll

No attributes. The event scroll gives the position (section 3).
  • A Scroll takes the utilities of scroll snapping: snap-x, snap-y or snap-both, with snap-mandatory or snap-proximity. A child takes snap-start, snap-center, snap-end and snap-always (layout spec, section 5.12, ADR-0128).
  • A sticky element stays in the visible area of its nearest Scroll, List or element with overflow-hidden (layout spec, section 5.7, ADR-0128).
  • A screen reader can scroll each element into view, as in a browser. Each Scroll and List around the element then scrolls, also for a disabled control (ADR-0142).

4.3 List

List takes items and key, and the fill #fill row(item) { ... }. Language spec, section 7.7, gives the rules of the row slot, and section 9.5 gives the rules of keys. List has no events of its own.
Attribute
Type
Default
rowHeight
Float
none
A List is a scroll area on its main axis, as Scroll is. Only the rows near its visible area exist: the rows that touch the area, and one area height above and below it (ADR-0027).
  • The runtime measures each row that it builds. A row that was never built has the average height of the first measured rows. With rowHeight, each row has that height, and the runtime does not measure. The rows must then have this height.
  • A List needs a bounded height to build fewer rows, for example grow in a Column with a height, or h-100. A container between the List and that Column needs min-h-0 (layout spec, section 3). A List without a bounded height grows to the height of all rows, so all rows exist.
  • A row that leaves the window is destroyed, with its state, its effects and its tasks. A row that comes back is a new row.
  • The rows do not shrink: a row has shrink 0 unless its style says else.
  • The List has the role "list". Each row is in a container with the role "list item", the number of items and the position of its item. A row keeps its own role.
  • A #for block builds all rows. Use List for a long list.
  • A List takes the utilities of scroll snapping and holds sticky elements, as a Scroll does (section 4.2). Only the rows near the visible area exist, so a snap area of a row that does not exist is no snap point.
  • A List in a window builds the rows of a tall window before its first layout pass, not all rows. A List in a hidden element keeps its rows until it shows again (ADR-0110).

4.4 Text

No attributes of its own. Events: doubleclick.Text accepts text (language spec, section 9.3). The web element is <span>. Utilities give the size, the weight and the color: style="text-20 font-600 text-$error".A Text takes text, values, blocks, Span and Link (ADR-0111). Its parts are one paragraph:
surfRun
<Text style="text-16"> Run <Span style="font-mono bg-$surface">surf dev</Span>, then read the <Link href="/docs">documents</Link>.</Text>
  • A line can break inside a part. The line height comes from the largest part on the line.
  • A screen reader gets the whole text of the Text, and a node for each Link in it.
  • A Text is not selectable, unless it or an ancestor has select-text (layout spec, section 5.10, ADR-0131). With select-text, a drag selects text, also across the parts of the Text, a double click selects a word, and the copy key copies it.

4.4a Span

A part of a Text with a style of its own (ADR-0111). The web element is <span>.
  • A Span is valid only in a Text or in another Span.
  • Attributes: title, lang and id, with style and testid. A Span has no events.
  • Its utilities are the utilities of text: color (text-$accent), font weight (font-600) and font family (font-mono). Size (text-14, text-sm), italic, underline, line-through, background color (bg-$surface) and text-nowrap are utilities of text too. With text-nowrap, a line breaks only before or after the Span, not inside it, so a short code sample such as a-b stays on one line. A state (hover:), a condition (dark:, {value}:), a case list and a computed value (text-{color}, text-{size}) work. Each other utility is a compile error.
  • Blocks and values work in a Span: #for t in tokens { <Span style="...">{t.text}</Span> } writes a code sample from a list of tokens.

4.5 Image

Attribute
Type
Default
src
String
none
srcset
String: a file for each pixel density
none
loading
String: eager or lazy
eager
Events: load (no value) and error (error: MediaError) (ADR-0160). alt (section 1) is the text of the image for a screen reader.
  • load comes when the file arrived and the picture has its size. error comes when the file did not load. Each file of an element sends one event, so a new src sends again. A lazy image sends after its load starts.
  • The case of MediaError (section 4.5b) is network for a file that did not arrive: a file that is not in the build, an HTTP error, or no connection. It is decode for data with the signature of a format that each target shows (PNG, JPEG, GIF, WebP and SVG), and format for other data. On the web, the runtime reads the file again after an error to find the case.
  • src is a path in the build or a URL. A file in src/assets/ of a project goes into each build, and its path is assets/logo.png (ADR-0033). The web build copies the folder next to the page, and the desktop build next to the binary, or into Contents/Resources of a macOS app bundle.
  • An Image has the size of its picture, one pixel of the file for each logical px. With a width or a height from the styles, the other side comes from the ratio of the picture. With both, the picture stretches to the content box, unless object-contain, object-cover or object-none says otherwise.
  • The native targets show PNG, JPEG, WebP, GIF and SVG. An animated GIF, an animated WebP and an animated PNG (APNG) play on all targets. They play with the delays of their frames and with their count of loops. A delay of 10 ms or less is 100 ms, as in the browsers (ADR-0131). Windows shows the first frame of an animated WebP, because its WebP codec gives no frames (docs/native-differences.md). A JPEG comes upright, as its EXIF orientation says. A file that does not load paints nothing there. An image from an http or https address loads in the background and has no size until it arrives (ADR-0037). An SVG file has the size of its view and the shapes of an Icon. A path in quotes under assets/ must name a file, or the compiler gives an error.
  • bg-url() takes the same paths and URLs for the background of each element, and the same formats and rules of the load. A literal path under assets/ must name a file (layout spec, section 5.8, ADR-0127). A background image is decoration with no alt text. Use an Image with alt for a picture with a meaning.
A file for each pixel density (ADR-0159).
surfRun
<Image src="assets/logo.png" srcset="assets/logo.png 1x, assets/logo@2x.png 2x" alt="Surface" />
  • srcset has the form of HTML with densities only: candidates with a comma between them. A candidate is a path or a URL, a space and a density with x, for example 2x or 1.5x. If srcset has no 1x candidate, src is the 1x candidate.
  • A width with w and the attribute sizes are a compile error.
  • The size of a file in logical px is its size in pixels, divided by its density. So a file of 200 × 100 pixels with 2x has 100 × 50 logical px, and each candidate gives the same layout.
  • A native target chooses the smallest density that is equal to or larger than the scale of the window. The scale is the count of device pixels for each logical px. If no candidate is that large, the largest density wins. A move to a screen with another scale, or a new scale of the UI, chooses again.
  • On the web, the browser chooses the candidate, as HTML allows. It can take a smaller file, for example to save data (see native differences).
  • In a literal srcset, each path under assets/ must name a file. The syntax must be valid, and two candidates must not have one density. A computed srcset gets these checks at run time, and the runtime skips a candidate with an error.
Lazy images (ADR-0159).
  • With loading="lazy", the image starts its load when its box comes near the visible area of its nearest Scroll, List or window. With eager, it loads at once.
  • On the native targets, "near" is one height and one width of that area, as the rows that a List builds (section 4.3). On the web, the browser chooses the distance, because HTML gives a page no way to set it.
  • A hidden image with lazy does not load. A hidden image with eager loads, as in HTML.
  • Before its load, an image without a size from its styles has no size. Give it a width and a ratio, for example w-full aspect-16/9, so that the layout does not move.

4.5a Link

A link (ADR-0051): an <a> on the web target, and a Button with the role of a link on the native targets.Props: href (String), target (String), alt. href takes a relative URL, http, https, mailto and tel. For another scheme, the web target writes no attribute (ADR-0109).target="_blank" opens the address outside the app (ADR-0160): in a new tab of the browser on the web, and in the browser of the system on the native targets. target takes only "_blank". A literal address of the app with target, for example href="/docs", is a compile error, because a native app has one window. A computed address of the app goes to the router of SurfWave on the native targets, as without target.Events: press, doubleclick, focus, blur, keydown. Link accepts text.With a handler for press, a press stays in the app. A press with a modifier key opens the address as the browser does. Without a handler, the browser follows the link. On a native target, a press without a handler opens an address with the scheme http, https, mailto or tel in the browser of the system. The system opens mailto and tel with the app for them. A relative URL goes to the router of SurfWave, and a link with another scheme does nothing.A Link in a Text is a part of the text (ADR-0111). It keeps the focus, the keys, the hover state, the router and the check of its URL. It takes the utilities of a Span and cursor-. A Link in a Link is a compile error.

4.5b Video and Audio

A video and a sound (ADR-0155, ADR-0156). The web elements are <video> and <audio>. Not in version 0: a video that autoplay started does not pause while no part of it is in the window.
surf
<Video poster="assets/intro.jpg" controls alt="Introduction" style="w-full rounded-8" bind:paused={paused} bind:currentTime={time}> <Source src="assets/intro.mp4" type="video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"" /> <Source src="assets/intro.webm" type="video/webm; codecs=\"vp9, opus\"" /> <Track src="assets/intro.en.vtt" kind="captions" srclang="en" label="English" default /></Video><Audio src="assets/chime.mp3" controls alt="Chime" />
Attribute
Type
Default
src
String
none
poster
String (only Video)
none
autoplay
Bool
false
loop
Bool
false
controls
Bool
false
preload
String: none, metadata or auto
metadata
paused
Bool, bindable
true
currentTime
Float, bindable: the time in seconds
0.0
volume
Float, bindable: from 0 to 1
1.0
muted
Bool, bindable
false
playbackRate
Float, bindable: from 0.25 to 4
1.0
metadata
MediaMetadata
the title from alt, the picture from poster
labels
MediaLabels
English texts
Events: play, pause, ended, timeupdate (time: Float), loadedmetadata (info: MediaInfo), volumechange (volume: MediaVolume), ratechange (rate: Float), nexttrack, previoustrack and error (error: MediaError). With controls, also focus, blur and keydown.
surf
type MediaInfo = { duration: Float, width: Float, height: Float }type MediaVolume = { volume: Float, muted: Bool }type MediaMetadata = { title: String = "", artist: String = "", album: String = "", artwork: String = "" }enum MediaError { network, decode, format }
Sources.
  • src is a path in the build or a URL with http or https, as the src of an Image (section 4.5). Version 1 plays a file. HLS, DASH and live streams are not in version 1.
  • A list of Source elements gives more files (section 4.5c). src and a Source child on one element are a compile error.
  • The element tries the sources in their order, as HTML does. It skips a source whose type the system cannot play, and it tries the next source after an error. After the last source, it sends error with format.
  • Each target plays the formats of its media system, and ADR-0155 has the table. The media systems are AVFoundation on macOS, Media Foundation on Windows, GStreamer on Linux and the browser on the web. For a video, give an MP4 with H.264 and AAC first, then a WebM with VP9 and Opus. Each target then plays one of them. For a sound, an MP3 plays on each target.
  • surf lint gives the finding media-format when the sources do not cover each target of the table. For a literal path under assets/, the compiler reads the format from the file.
  • A target with no media system sends error with format, for example Linux without GStreamer. The poster stays.
  • The .deb of surf package for an app with media recommends gstreamer1.0-libav, so that Ubuntu plays H.264 and AAC (ADR-0155).
Events.
Event
When
play
The element starts to play, also after a pause.
pause
The element pauses: a press, the media controls of the system, or the end without loop.
ended
The play reached the end without loop.
timeupdate
Each 250 ms while the element plays, and after a seek of the user, a pause and the end. The value is the time in seconds. The rate is the same on all targets.
loadedmetadata
The media system read the duration and the size of the file. width and height are in logical px, and they are 0 for a file without video.
volumechange
The user changed the volume or muted.
ratechange
The user changed the rate through the media controls of the system.
nexttrack, previoustrack
The user pressed "next" or "previous" in the media controls of the system.
error
No source plays (format), the network failed (network), or the file has an error (decode).
Image gets on:load and on:error with the same MediaError in P6.22.Bindings (ADR-0156).
  • A Video and an Audio have five bindable props: paused, currentTime, volume, muted and playbackRate. Each other built-in component has at most one (section 2). The rules of bind: of language spec 7.4 and 9.2 apply.
  • The element writes the bound place at its events: paused at play and pause, currentTime at each timeupdate, volume and muted at volumechange, and playbackRate at ratechange. The write comes before the on: handlers of the event (section 3.2).
  • A write of the program changes the media. paused = false plays, under the rule of sound below, and paused = true pauses. A new currentTime seeks to that exact time. A new volume, muted or playbackRate applies at once.
  • A change of the program and a change of the media do not make a loop. The element keeps the last value of each prop: the value that it wrote, or the value that it took from the program. A new value that is equal to the kept value does nothing. Only a different value changes the media. So the write at each timeupdate never comes back as a seek.
  • A change of the program sends no event (section 3.2). The next events come from the play.
  • A value outside its range applies as the nearest value of the range. The range of currentTime is from 0 to the duration, of volume from 0 to 1, and of playbackRate from 0.25 to 4. The pitch of the sound stays at each rate.
  • Before the metadata arrives, the element keeps a new currentTime, and it seeks when the metadata arrives.
  • If the rule of sound refuses a play, the element stays paused. It writes true to the bound paused again at the end of the batch, with no event.
  • Without bind:, a prop such as muted={quiet} sets the element. A change of the user stays until the program gives a new value, as the text of an Input (section 4.7).
  • The duration and the size are read-only. They come from loadedmetadata.
Size and paint.
  • A Video has the size of its video, one pixel of the file for each logical px, as an Image. Before the metadata arrives, it has the size of its poster, or else 300 × 150 logical px. A width or a height from the styles keeps the ratio.
  • The picture fits into the box with object-contain by default. object-cover, object-fill and object-none work, as on an Image. The free area shows the background of the element.
  • A frame paints as each other element: with the clip, the radius, the opacity, the transform and the mask of its element. Another element can paint over it.
  • The poster shows until the first frame of the play, or until a seek. Without a poster, the first frame shows when preload loaded it.
  • An HDR video shows in SDR on the native targets (see native differences).
  • An Audio without controls has no box and is not in the accessibility tree, as in HTML. With controls, its box is the bar of the controls: 300 logical px wide by default.
Autoplay, sound and motion (ADR-0158).
  • autoplay starts the element when its media is ready.
  • With the setting "reduce motion" on, autoplay does not start a Video. The poster or the first frame shows. When the setting comes on, a video that autoplay started pauses. A video that the user started plays on.
  • A video that autoplay started pauses while no part of it is in the window, and it plays again when a part comes back. The rule of the event visible (section 3) applies.
  • One rule of sound applies on all targets, the rule of the strictest browser:
  • An element plays with sound only when its play starts in the batch of an event of the user. The events are a press, a key and a pointer button.
  • autoplay without muted is a compile error for a literal. With a computed muted, autoplay starts only while muted is true.
  • A play with sound at another time does not start, and the element stays paused.
  • A muted element that plays gets its sound only in the batch of an event of the user. At another time, it pauses.
  • surf lint gives the finding autoplay-stop for a Video with autoplay and loop, without controls and without bind:paused.
The media controls of the system (ADR-0156).
  • The media keys of the keyboard and the media controls of the system control one element. It is the element that last started to play with sound. The controls are "Now Playing" on macOS, the media controls of Windows, MPRIS on Linux, and the Media Session API on the web.
  • "With sound" means that muted is false, volume is above 0 and the file has a sound track. A file shorter than 5 seconds does not connect, so a short sound of the UI does not take the media keys.
  • The element stays connected while it plays and while it pauses, until another element connects or it leaves the tree.
  • The system can play, pause and seek. A change through it is a change of the user: it writes the bound places and sends the events.
  • "Next" and "previous" send nexttrack and previoustrack. The system shows these buttons only when the element has a handler for them.
  • The system shows the title, the artist, the album and the picture of metadata, and the duration, the position and the rate. artwork is a path or a URL, as src of an Image. Without metadata, the title is alt and the picture is poster.
  • On Linux, the name of the app on the session bus is org.mpris.MediaPlayer2. and the name of its binary. When another process has this name, the app adds .instance and its process id, as MPRIS suggests.
Other rules.
  • An element that leaves the tree stops. hidden does not pause, as in HTML.
  • The play goes on while the window is behind another window, or in the Dock or the taskbar.
  • alt is the name for a screen reader. The role is "video" or "audio". surf lint gives the finding no-name for a Video without alt or decorative.

4.5c Source and Track

Source and Track are valid only in a Video or an Audio. A Video and an Audio take only Source, Track and blocks as children.
Element
Attribute
Type
Default
Source
src
String
required
Source
type
String: a MIME type with codecs, as in HTML
none
Track
src
String: a WebVTT file
required
Track
kind
String: captions or subtitles
subtitles
Track
srclang
String: a language tag
none
Track
label
String
the value of srclang
Track
default
Bool
false
Source (ADR-0156).
  • Without type, the target must load the file to know its format. Give type with the codecs, so that a target skips a file that it cannot play.
Track (ADR-0157).
  • captions give the speech and the sounds, for a person who cannot hear. subtitles translate the speech. The kinds descriptions, chapters and metadata are a compile error in version 1.
  • The file is WebVTT. A literal path under assets/ must name a file that parses as WebVTT. An error gives the line of the file.
  • Version 1 has these parts of WebVTT:
  • The timings of each text cue.
  • The settings line, position, size and align.
  • The tags <b>, <i>, <u>, <v>, <lang> and <c>. A <c> gives no style.
  • The character references.
  • The other parts of WebVTT are not in version 1. In a literal file, regions, vertical text, STYLE blocks, <ruby> and the timestamps in a text cue are a compile error. In a file from a URL, they show as plain text on the native targets.
  • The text cues have the default look of WebVTT: white text on black with 80 % opacity, and a sans-serif font. The font size is 5 % of the height of the video. They are at the bottom of the video, and they move up while the bar of the controls shows.
  • On the web, the browser draws the text cues. It applies the caption style of the system where it reads it, or its own caption settings. While the bar shows, the runtime sets the line of each text cue with an automatic line, so that the text cue ends above the bar.
  • On the native targets, the runtime draws the text cues with the caption style of the system: MediaAccessibility on macOS, and ClosedCaptionProperties on Windows. Linux has no such setting, so the text cues have the default look of WebVTT. A change of the setting applies to the next frame.
  • At the start, the first track with default shows. Without default, no track shows. The button of the captions chooses a track or "Off".
  • A screen reader does not read the text cues aloud, as in each browser.
  • surf lint gives the finding no-captions for a Video without muted and without a Track of the kind captions.
  • Captions inside the media file, for example CEA-608 in an MP4, do not show in version 1.

4.5d The controls of Video and Audio

With controls, an element shows the bar of the runtime on all targets, also on the web (ADR-0156). The browser does not show its own controls. Not in version 0: in the full screen, the pointer does not hide. The Tab key also reaches the elements outside the video. The X11 fallback of Linux keeps its window. The button of the captions counts only the Track elements outside a block.The bar is a component of the compiler. Only an app with controls has its code. On an Audio, the bar is the box of the element: a group with the name of alt. A testid of the element names the bar.
  • The bar has these parts:
  • A button to play and to pause.
  • The time and the duration as m:ss, or as h:mm:ss from one hour.
  • A Slider for the position.
  • A button to mute, and a Slider for the volume.
  • A button for the captions, when the element has a Track. With more than one track, the button opens a Menu of the tracks and "Off".
  • On a Video, a button for the full screen.
  • On a Video, the bar is at the bottom of the video, with white icons on a layer of 60 % black. On an Audio, the bar uses the theme tokens.
  • The bar of a Video shows in three cases:
  • While the video does not play.
  • For 3 seconds after each move of the pointer over the video.
  • While a control of the bar has the focus.
  • The bar takes 150 ms to fade in or out. With the setting "reduce motion", it shows and hides at once.
  • A change through the bar is a change of the user. It writes the bound places and sends the events.
  • labels gives the texts of the bar for a screen reader, with English defaults:
  • surf
    type MediaLabels = { play: String = "Play", pause: String = "Pause", position: String = "Position", mute: String = "Mute", unmute: String = "Unmute", volume: String = "Volume", captions: String = "Captions", captionsOff: String = "Off", fullScreen: String = "Full screen", exitFullScreen: String = "Exit full screen",}
The full screen.
  • A press on the button of the full screen shows the video, its text cues and its bar on the whole screen.
  • On the web, the runtime calls requestFullscreen() on its element that holds the video, the text cues and the bar.
  • On a native target, the window goes into the full screen mode of the system. The video then covers the window above each other element, on black, with object-contain.
  • Escape, the button and the way of the system end the full screen. A window that was in the full screen mode before stays in it.
  • In the full screen, only the video and its bar are in the accessibility tree. The Tab key reaches only the video and the controls of its bar, as in a modal Overlay. The focus stays on the button. The pointer hides with the bar.
  • Only a press of the user starts the full screen. The program has no function for it in version 1.
Keys. The keys follow the WAI-ARIA Authoring Practices.
  • Each button and each slider of the bar is a stop of the Tab key. They have the keys of a Button (section 4.6) and of a Slider (section 4.10).
  • The keys move the slider of the position by 5 seconds, and its value stays exact, as the keyboard step of a WAI-ARIA slider. Its value for a screen reader is a text, for example "1:05 of 3:20". The slider of the volume has a step of 0.1.
  • A Video with controls is also a stop of the Tab key. While it has the focus, Space plays or pauses, and the left and right arrow keys move the position by 5 seconds.
  • The bar has no letter keys.

4.6 Button

Attribute
Type
Default
disabled
Bool
false
Events: press, doubleclick, focus, blur, keydown. Button accepts text.

4.7 Input

Attribute
Type
Default
value
String, bindable
""
placeholder
String
""
disabled
Bool
false
autofocus
Bool
false
selectOnFocus
Bool
false
Events: input (text: String), change (text: String), submit, focus, blur, keydown.With autofocus, the control gets the keyboard focus at the end of the batch that puts it into the tree. When more than one control has it, the last one gets the focus. With selectOnFocus, the control selects its text each time it gets the focus. A press that gives the focus keeps this selection, unless a drag selects a part of the text, as in a browser.Only a new value from the program replaces the text. A control without a binding keeps the text that the user typed, also after the loss of the focus (ADR-0113).An Input has one line. It removes each line break from its text: from a value of the program and from a paste. The state keeps its line breaks until the next input event (ADR-0113).Enter after an edit sends change and then submit. The loss of the focus after an edit sends change too, but only once for one edit (section 3.2, ADR-0113).The text of an Input and of a TextArea stays selectable on all targets, also without select-text and with select-none (layout spec, section 5.10, ADR-0131). CSS UI 4 gives an editable element the used value contain, so select-none has no effect on a field, and surf lint gives the finding field-select for it. selectOnFocus selects the text, also with select-none.

4.8 TextArea

The attributes of Input. Events: input (text: String), change (text: String), focus, blur, keydown. TextArea has no submit, because Enter starts a new line.

4.9 Toggle

Attribute
Type
Default
checked
Bool, bindable
false
disabled
Bool
false
Events: change (checked: Bool), focus, blur, keydown. Toggle accepts text: the label.

4.10 Slider

Attribute
Type
Default
value
Float, bindable
50.0
min
Float
0.0
max
Float
100.0
step
Float
1.0
disabled
Bool
false
Events: input (value: Float), change (value: Float), focus, blur, keydown.The slider rounds its value to a step from min and keeps it between min and max. The order of the attributes has no effect.

4.11 Drawing and the shapes

A Drawing is a box with its own coordinates, the view (ADR-0029). The shapes are valid only in a Drawing, or in a Group in a Drawing, and a Drawing takes only shapes and Group. Text is not a shape: put a Text above the drawing in a Stack.
surf
<Drawing viewWidth={200.0} viewHeight={100.0} alt="Transfer curve" style="w-full"> <Polyline points={curve} style="stroke-$text stroke-2 stroke-join-round" /> <Circle cx={handle} cy={20.0} r={8.0} alt="Handle" style="fill-$accent" on:pointerdown={grab} on:pointermove={drag} /></Drawing>
Element
Attributes (Float unless the table says otherwise)
Drawing
viewWidth, viewHeight: the size of the view
Line
x1, y1, x2, y2
Polyline, Polygon
points: List<Float>: x and y, one after the other
Rect
x, y, width, height, radius (0 by default)
Circle
cx, cy, r
Ellipse
cx, cy, rx, ry
Path
d: String: SVG path data with the commands M, L, H, V, C, S, Q, T, A and Z, absolute and relative
Group
none: it groups shapes for style, opacity and alt
  • The view maps into the content box of the Drawing with the same scale on both axes, in the middle of the box. Without a width from its styles, a Drawing has the width of its view. Its height comes from the ratio of the view (layout spec, section 4).
  • A shape has no layout. A change of an attribute of a shape paints again, but does not run the layout.
  • The paint of a shape comes from the utilities fill-, stroke-, stroke-<n>, stroke-dash-, stroke-cap- and stroke-join- (layout spec, section 5.11), and from opacity-. A Group gives its paint to its shapes. The states, dark:, the directives and computed values work as on other elements: hover:stroke-$accent, stroke-{color}.
  • A shape and a Group have no box. They take only these utilities, a text color (text-$accent, the color of a fill with no color of its own) and cursor-. Another utility is a compile error, for example rotate-45, shadow-md or p-2 (ADR-0120). To move or turn a shape, change its coordinates, or put the transform on the Drawing.
  • A shape gets the pointer events (section 3.1) when the pointer is inside its fill, or within half its stroke width of its stroke. A fill or a stroke of none gets no events.
  • A Drawing with alt is an image for a screen reader. A shape with alt is a button when it has a pointerdown handler, else an image. A press of a screen reader on such a button sends pointerdown and pointerup in the middle of the shape.

4.12 Icon

surf
import close from "./icons/close.svg";prop heldIcon: Icon = close;---<Button on:press={cancel}> <Icon src={close} alt="Cancel" style="text-$accent" /></Button><Icon src={held ? heldIcon : close} style="w-6 h-6" />
Attribute
Type
Default
src
Icon
required
No events of its own. It has the pointer events of section 3.1, and the whole box gets them: x and y are in the border box, as for an Image.
  • An Icon is a value of the built-in type Icon. A prop, a state, a field or a list item can hold it. == compares two icons.
  • The icon has the text color: a shape without a fill, and a fill or a stroke of currentColor, take the text color of the Icon. The other colors of the file stay.
  • Without a size from its styles, an Icon has the height of its font size and the ratio of its view. w- and h- change each side: give both for another size. The view stays in the middle of the content box.
  • alt makes it an image for a screen reader. Without alt, a screen reader skips it.
  • An SVG file can have svg with viewBox (or width and height), g, path, circle, ellipse, rect, line, polyline and polygon, with the attributes fill, stroke, stroke-width, stroke-linecap, stroke-linejoin, stroke-dasharray, opacity, fill-opacity and stroke-opacity, also in a style attribute. Other elements, transform and fill-rule="evenodd" are compile errors. The opacity of a g multiplies the opacity of each of its shapes.
  • Icon.parse(text) gives an icon from SVG text at run time, with the same rules (ADR-0030).

4.13 Select and Option

surf
<Select bind:value={mode} on:change={v => save(v)} alt="Oversampling"> <Option value="off">Off</Option> #for f in factors { <Option value={f}>{f}x</Option> }</Select>
Attribute
Type
Default
value (bindable)
String
the first option
disabled
Bool
false
autofocus
Bool
false
system
Bool
false
Events: change (value: String), focus, blur, keydown.An Option has value: String and disabled: Bool, and its text is its label. Option is valid only in a Select, and a Select takes only Option elements, an OptionList and blocks. Without a value that matches an option, a Select shows the first option.On all targets, the closed box of a Select takes bg-, border and rounded-, and it has an arrow at its end. Without them, it has the colors $surface and $border.A press opens the list of the runtime, which is the same on all targets (ADR-0059). The list is below the box, or above it when it does not fit. The arrow keys move the selection, Enter takes it, and Escape or a press outside closes the list. A press on a row takes it at once.The list takes styles like each other element. An Option takes style for its row, with hover:, focus-visible: and style: directives. The optional OptionList holds the options, and its style applies to the box of the open list: its background, border, radius, padding, gap and size. Without styles, the list has the colors of the theme.
surf
<Select bind:value={mode} style="w-30 rounded-8"> <OptionList style="gap-0.5 p-1 rounded-8 border bg-$surface"> #for f in factors { <Option value={f} style="px-2 py-1 rounded-4 hover:bg-$accent {f == mode}:font-600"> {f} </Option> } </OptionList></Select>
With system, a Select opens the control of the system instead, and an app accepts its look and its timing. system takes no value from a state. On macOS this is the menu of the system, in dark with a dark app (ADR-0042). macOS reports a pick more than 400 ms after the click. On the web it is the list of the <select> of the browser (ADR-0040). On Linux, which has no such control, it is the list of the runtime. A system control takes no styles for its list.

4.14 Overlay

surf
<Stack> <Button on:press={open = true}>Presets</Button> #if open { <Overlay side="bottom" on:close={open = false}> <Column style="bg-$surface p-2 rounded">...</Column> </Overlay> }</Stack>
Attribute
Type
Default
side
String: top, bottom, left, right, start or end
bottom
offset
Float
4
modal
Bool
false
focus
Bool
false
passive
Bool
false
Events: close.
  • An Overlay is above all other elements. No overflow-hidden or Scroll of its parents cuts it, and it takes no space in its parent. It is a flex column as large as its content.
  • Its parent element is its anchor. It starts at the start edge of the anchor, offset from it on side. When it does not fit in the window there, it goes to the opposite side, and then it moves into the window.
  • start and end are logical sides (ADR-0124). start is the left side and end is the right side, unless the overlay has the direction rtl (layout spec, section 5.2). Then they change places. So above and below an anchor, an overlay with the direction rtl starts at the right edge of the anchor.
  • A press outside the last open Overlay, or Escape, sends it close. That press does not reach the element below. The app removes the overlay in its handler, or keeps it.
  • With modal, the overlay covers the window with a dark layer, and its content is in the middle. A press on the dark layer sends close.
  • With modal or focus, the first control in the overlay gets the focus when it opens. A control with autofocus wins (ADR-0102).
  • Tab and Shift+Tab stay in a modal overlay. In an overlay with focus, Tab sends close.
  • Each overlay gives the focus back when it closes, if the focus went away with it.
  • With passive, a press outside it and Escape pass through it, and it sends no close. A tooltip uses this. On the web, the control with the focus has aria-describedby with the id of the overlay while it shows.
  • With modal, it is a dialog for a screen reader, with aria-modal. Else alt makes it a named group. A role wins.

4.15 Transition

Props: enter (String), exit (String), ms (Int, default 150), ease (String, default ease), delay (Int, default 0), stagger (Int, default 0), from (String, default start), move (Bool).
surf
<Transition enter="opacity-0 translate-y-2" exit="opacity-0" ms={200} ease="ease-out"> #if open { <Column>...</Column> }</Transition>
from
The extra time of element i of n
start
i * stagger
end
(n - 1 - i) * stagger
center
floor(abs(2 * i - (n - 1)) / 2) * stagger
  • Transition has no box. Its children are children of its parent for the layout and for the inherited styles. It takes no style and no events.
  • enter gives the utilities of the start of an enter, and exit the utilities of the end of an exit. It needs at least one of them, or move. They can set opacity, transform, background, color and shadow. exit can also set absolute and the utilities of inset, also the logical sides inset-s- and inset-e- (ADR-0124). These apply at the start of the exit. A value replaces the whole property.
  • move animates an element that changes its place, from its old place to its new place (ADR-0081). A #for needs a key for this. move needs no enter and no exit.
  • ms, delay and stagger are numbers in the source, in ms. ease is the name of an easing utility of the layout spec, section 12.4, as ease of a motion (language spec 7.5, ADR-0104). Examples are ease-linear, ease-out-cubic, ease-spring and ease-spring-0.4. A reader can then copy the name from a style.
  • A name without ease-, for example spring or linear, is a compile error. The message gives the correct name, for example ease-spring.
  • delay holds back each enter, exit and move.
  • stagger adds a time for each element of one batch, so that the rows of a list start one after the other (ADR-0104). The elements that start in one frame are one batch, in tree order. A new row that comes alone has no extra time. from gives the first element of the batch:
  • surf
    <Transition enter="opacity-0 translate-y-2" ms={200} stagger={40} from="center"> #for row in rows key row.id { <Text>{row.title}</Text> }</Transition>
  • A row that starts by its index for a change of its style uses a computed delay: delay-{i * 40} (layout spec 12.1).
  • An element animates if a #if, #for, #match or #await inside the Transition adds or removes it, and only blocks are between the two. A nested element goes with its parent. A text node goes at once. The rows of a List do not animate.
  • The block goes inside the Transition. A Transition inside a block goes with that block.
  • The children of its first update do not play an enter. A change of the theme or the window width plays no enter.
  • With the setting of reduced motion on, an element comes and goes at once (layout spec 5.10).
  • During an exit, the element keeps its place in the layout, gets no events and leaves the accessibility tree. It loses the focus. Its values no longer change. Its siblings close the gap at the end. With exit="absolute", it leaves the flow at the start of the exit, and in a Stack this gives a cross-fade.
  • A block with no other element can show again during an exit. The new element of the same kind then starts at the shown values of the old element, and the old element goes at once. A new row beside other rows does not do this: the row that leaves plays to its end. Two branches that change in the same frame both play.

4.16 Components that version 0 does not support yet

Canvas and PlatformView are in the language spec, section 9.6, but this reference does not define them yet. Each of them is a compile error in version 0.
  • Canvas is a drawing function. Use a Drawing with shapes (section 4.11).
  • PlatformView comes later.

5. The components of surf:ui

surf:ui is a built-in module (ADR-0102). Each component is a .surf file in the compiler, so it works on each target. An app that does not import a component has no code of it.
surf
import { Dialog, Tabs, UiItem } from "surf:ui";
These rules apply to each component:
  • It has the roles, the names and the keys of the WAI-ARIA Authoring Practices. On the native targets, the same roles go through AccessKit.
  • It uses the theme tokens (layout spec, section 2.1), so it works in each variant of the theme.
  • A style on an instance applies to its root elements (language spec, section 9.2).
  • Its motion stops with the setting "reduce motion" of the system.
  • A text in a component, such as closeLabel, is a prop with an English default.
  • A component name of surf:ui is a name of the project. A file with the same name in the app is a compile error when the app imports that component.
Tabs, RadioGroup, Accordion and Menu take a list of UiItem:
surf
type UiItem = { value: String, label: String, disabled: Bool = false }
The module also has Tone, the enum of Badge and Toast, with the cases neutral, accent, success, warning and error. A parent writes tone={.warning} with no import.

5.1 Card

A surface for one topic. It is a group, and heading is its name.
surf
<Card heading="Plan"> <Text>10 seats</Text> #fill footer { <Text>Renews in May</Text> }</Card>
Prop
Type
Default
heading
String
"": no heading
level
Int
3: the level of the heading, 2, 3 or 4. Another value is 3
Slots: default, footer?. The heading has the role heading. Give a card directly under the heading of the page the level 2, so that the levels of the page skip no level (ADR-0110).

5.2 Badge

A small label for a state or a count.
surf
<Badge tone={.success}>Paid</Badge>
Prop
Type
Default
tone
Tone
.neutral
Slot: default, which accepts text.

5.3 Separator

A line between two parts of a page. It has the role separator and an orientation.
surf
<Separator /><Separator vertical />
Prop
Type
Default
vertical
Bool
false

5.4 Avatar

A picture of a person, or the initials of the name. It is an image, and name is its name.
surf
<Avatar name="Ada Lovelace" src="assets/ada.png" /><Avatar name="Grace Hopper" />
Prop
Type
Default
name
String
required
src
String?
none: the initials

5.5 Spinner

A wait with no known end. It has the role progressbar and no value. With "reduce motion", the ring stands still.
surf
<Spinner alt="Loading the list" />
Prop
Type
Default
alt
String
"Loading"

5.6 Progress

A bar for the progress of a task. It has the role progressbar, with valueNow, valueMin and valueMax. Without value, the end is not known, and the bar pulses. With "reduce motion", the bar stands still.
surf
<Progress alt="Upload" value={percent} /><Progress alt="Waiting" />
Prop
Type
Default
alt
String
required
value
Float?
none
max
Float
100.0

5.7 Radio

One choice with a ring and a label. It is a Button with the role radio. Use a RadioGroup for a group: it gives the keys. A Radio alone suits one choice in a layout of the app.
surf
<Radio checked={agree} on:press={agree = !agree}>I agree</Radio>
Prop
Type
Default
checked
Bool
false
disabled
Bool
false
tabStop
Bool
true: the Tab key reaches it
id
String
""
Events: press, key (name: String). Slot: default, which accepts text.

5.8 RadioGroup

A choice of one item. It has the role radiogroup.
surf
<RadioGroup alt="Size" items={sizes} bind:value={size} />
Prop
Type
Default
items
List<UiItem>
required
value
String, bindable
""
alt
String
required
horizontal
Bool
false
Event: change (value: String).
Key
Effect
Tab
Goes to the checked radio button, and then leaves the group.
Up, Left
Checks the previous item, around the ends, past a disabled item.
Down, Right
Checks the next item.
Home, End
Checks the first or the last item.
Space
Checks the item with the focus.

5.9 Tabs

One panel at a time. The tabs have the role tab, and the panel has the role tabpanel. The panel follows the tab at once.
surf
<Tabs alt="Settings" items={pages} bind:value={page}> #fill panel(item) { <Text>{item.label}</Text> }</Tabs>
Prop
Type
Default
items
List<UiItem>
required
value
String, bindable
"": the first item that is not disabled
alt
String
required
rtl
Bool
false. true: the tabs go from right to left, for right-to-left text (ADR-0122)
Event: change (value: String). Slot: panel(item: UiItem).
Key
Effect
Tab
Goes to the chosen tab, and then to the panel.
Left, Right
Chooses the previous or the next tab, around the ends, past a disabled tab. With rtl, Left chooses the next tab, and Right the previous tab.
Home, End
Chooses the first or the last tab.
The runtime does not know the direction of the text of an element, so an app in right-to-left text gives rtl to each Tabs.

5.10 Accordion

Sections that open and close. Each header is a heading of level 3 with a Button. Each open panel has the role region, and its header names it.
surf
<Accordion items={sections} bind:open={shown} multiple> #fill panel(item) { <Text>{item.label}</Text> }</Accordion>
Prop
Type
Default
items
List<UiItem>
required
open
List<String>, bindable
[]: the values of the open sections
multiple
Bool
false: one section at a time
Event: change (value: String). Slot: panel(item: UiItem).
Key
Effect
Enter, Space
Opens or closes the section.
Up, Down
Goes to the previous or the next header, around the ends.
Home, End
Goes to the first or the last header.

5.11 Dialog

A modal dialog above a dark layer. It has the role dialog and aria-modal, and heading is its name.
surf
<Dialog heading="Delete the file?" bind:open={asking}> <Text>This action cannot be undone.</Text> #fill actions { <Button on:press={asking = false}>Cancel</Button> }</Dialog>
Prop
Type
Default
open
Bool, bindable
false
heading
String
required
closeLabel
String
"Close". Empty: no close button
Event: close. Slots: default, actions?.
  • When the dialog opens, the first control gets the focus. An Input with autofocus wins.
  • Tab and Shift+Tab stay in the dialog, around its ends.
  • Escape, a press on the dark layer and the close button close it, and send close.
  • When the dialog closes, the control that opened it gets the focus back.

5.12 Tooltip

A short text for the control in it. The text shows while the control has the focus, and after a pointer rests on the control. It describes the control for a screen reader while it shows.
surf
<Tooltip text="Saves the file"> <Button on:press={save()}>Save</Button></Tooltip>
Prop
Type
Default
text
String
required
side
String: top, bottom, left, right, start or end, as the side of an Overlay (section 4.14)
"top"
ms
Int
500: the rest of the pointer before the text shows
Slot: default. Escape hides the text. The pointer can go onto the text, and the text stays (WCAG 1.4.13).

5.13 Menu

A button that opens a list of actions. The button has hasPopup="menu", and the rows have the role menuitem.
surf
<Menu label="Actions" items={actions} on:select={v => run(v)} />
Prop
Type
Default
label
String
required
items
List<UiItem>
required
disabled
Bool
false
alt
String
"": the name of the button for a screen reader. Empty: the label. The name must hold the label, for voice control
tabStop
Bool
true. false: the Tab key does not reach the button, for a list with one stop
Event: select (value: String).
Key
Effect
Enter, Space, Down on the button
Opens the menu on the first item.
Up on the button
Opens the menu on the last item.
Up, Down
Goes to the previous or the next item, around the ends, past a disabled item.
Home, End
Goes to the first or the last item.
A letter
Goes to the next item that starts with the letter.
Enter, Space
Runs the item, and closes the menu.
Escape, Tab
Closes the menu.
When the menu closes, the button gets the focus back.

5.14 Toast

A short message that goes away by itself. The region has the role status and is always in the tree, so a screen reader says each new message. With tone={.error}, it has the role alert.
surf
<ToastList> <Toast bind:open={saved} tone={.success}>The file is saved.</Toast></ToastList>
Prop
Type
Default
open
Bool, bindable
false
tone
Tone
.neutral
ms
Int
5000. 0: it stays until the user closes it
closeLabel
String
"Close"
restart
Int
0. A new value starts the time again, for a new message in an open toast
Event: close. Slots: default, which accepts text, and action?, for a button such as "Undo".
surf
<Toast bind:open={shown} restart={round} ms={10000}> {notice} #fill action { <Button on:press={undo()}>Undo</Button> }</Toast>
A Toast is in the flow of its parent. A ToastList gives the toasts of a window their place.

5.15 ToastList

The place of the toasts of a window (ADR-0122). It is fixed, at the bottom right of the window (layout spec, section 5.7). The toasts in it stack upward in the order of the source, so two messages at the same time do not cover each other.
surf
<ToastList> <Toast bind:open={saved} tone={.success}>The file is saved.</Toast> <Toast bind:open={failed} tone={.error} ms={0}>The file was not saved.</Toast></ToastList>
Slot: default. Put each Toast of a window in one ToastList.