Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

UI elements

Every built-in element, with all its settings. Settings marked required must be given; the rest are optional.

Kinds of value:

  • text: any expression that gives a string; stays up to date, e.g. "{count} left".
  • fixed text: a string written directly, without {…}, e.g. "Todos".
  • number: a number written directly, between 0 and 10000, e.g. 12.
  • fixed int: a whole number written directly, between -1000000000 and 1000000000, e.g. -10.
  • int: any expression that gives an int; stays up to date, e.g. item.percent.
  • list of text: any expression that gives a [string]; stays up to date.
  • option: one of a fixed set of .names, e.g. .center.
  • flag: true or false, written directly.
  • binding: a $ place, e.g. $draft. See Two-way bindings.

Unnamed settings come first, in order; named ones follow in any order.


window

A window. Only at the top level of a file. An app needs at least one.

window("Todos", width: 360, height: 480) { … }
window settings("Settings", open: false) { … }
SettingKind
(first) titlefixed textrequired
widthnumberdefault 480
heightnumberdefault 320
backdropoption.standard (default), .translucent, .solid
openflagtrue (default): it opens when the app starts; false: it waits for code to open it, and needs a name

A name written between window and the title, as in window settings("Settings"), is what open_window and close_window open and close it by. Window names share the program’s namespace with its states, functions, views and structs. An app needs at least one window that opens when it starts.

width and height are the size of the content area. The window can’t be made narrower than its content needs: it opens wider if the content doesn’t fit, and the user can’t drag it narrower. It can be made as short as the user likes, because a long list would otherwise make the window taller than the screen.

On macOS the title bar is transparent and the content area runs underneath it, the way Finder and Mail look. Content still starts below the title bar; only a surface that starts at the top of the window reaches up under it. On Windows the title bar sits above the content area, as usual.

Backdrops are drawn by the system, so they cost the app nothing:

backdropmacOSWindows 11
.standardthe usual windowMica in the title bar and frame
.translucentthe whole window shows a blurred view of what’s behind it, where no surface covers itAcrylic in the title bar and frame
.solidthe usual windowno material: a solid title bar and frame

On Windows the material stays in the title bar and frame, and the content area keeps its solid Fluent colors. The system’s own controls can’t yet draw on a see-through content area. Windows 10, Windows 11 before version 22H2, and high contrast themes draw every backdrop as .solid.

Block: the window’s content (required).


column

Stacks its children top to bottom. See Layout.

column(spacing: 12, padding: 20, align: .center) { … }
SettingKind
spacingnumberspace between children
paddingnumberspace around the edge
alignoption.stretch (default), .start, .center, .end
justifyoption.start (default), .center, .end, .space_between
surfaceoption.none (default), .sidebar, .content, .card; what the stack is drawn on
widthnumbera fixed width; without it the stack fits its content
max_widthnumberthe stack fills the width it’s given, up to this
growflagfalse (default); true takes the space the parent has left along its direction

Surfaces are drawn behind the stack’s children with each platform’s own materials, so they follow dark mode and high contrast:

SurfaceUse it formacOSWindows
.sidebara window’s side paneltranslucent sidebar material that shows the desktop through ita solid panel a shade apart from the window
.contentthe window’s main panethe color content is read on: white, or dark in dark modeFluent’s content layer color
.cardgrouping related contenta rounded panel with a thin outlinea rounded panel with a thin outline

A surface that starts at the top of the window reaches up under the macOS title bar, so a sidebar runs the full height of the window. Its children still start below the title bar.

max_width keeps text readable when a window is maximized: put the stack in a parent with align: .center and it stays centred at that width, or narrower when the window is.

row() {
  column(surface: .sidebar, width: 200, padding: 12) { … }
  column(surface: .content, grow: true, align: .center) {
    column(max_width: 600, padding: 24) { … }
  }
}

Block: the children.


row

Places its children left to right. Same settings as column.

row(spacing: 8, align: .center) { … }

Block: the children.


scroll

Stacks its children top to bottom, like column, and scrolls them up and down when they are taller than the space it has.

scroll(spacing: 8, padding: 12) {
  for item in downloads {
    DownloadRow(item: item)
  }
}

Same settings as column, but grow: a scroll area always takes the space its parent has left along its direction, like spacer, and stretches across it. It adds nothing to its parent’s size, so put it straight in a window, or in a stack that has space to give, such as one with grow: true; in a stack that fits its content it gets no space. A surface stays put behind the content as it scrolls.

macOSWindows
Scrollingthe trackpad, mouse wheel or scroll bar, with the system’s elastic bouncethe mouse wheel, by as many lines a notch as the user set, or the scroll bar
Scroll barfloats over the content, and shows while scrolling; with Show scroll bars: Always, it takes a strip beside the content and always showstakes a strip beside the content, and always shows, greyed out when there’s nothing to scroll

On Windows, moving the keyboard focus to a control that’s scrolled out of view doesn’t yet scroll it into view.

Block: the children.


label

Shows text.

label("{left} left", style: .caption)
label("Couldn't save", color: .danger, align: .center)
label(token, selectable: true)
SettingKind
(first) texttextrequired
styleoption.body (default), .large_title, .title, .headline, .caption
coloroption.primary, .secondary, .tertiary, .accent, .success, .warning, .danger
alignoption.start (default), .center, .end
selectableflagfalse (default); true lets the user select and copy the text

color defaults to .secondary for .caption and .primary for every other style. The colors are the system’s own, so they follow the user’s accent color, dark mode and high contrast on macOS and Windows, and change while the app runs when the user switches.

align places the text within the label’s frame, so it only shows when the label is wider than its text, as it is in a column with the default align: .stretch. .start and .end follow the reading direction: in a right-to-left language, .start is the right edge.

Block: none.


button

Runs its block when clicked.

button("Add", style: .primary) { add_todo() }
button("Add", icon: .plus) { add_todo() }
button("Delete", style: .icon, icon: .trash) { delete(todo.id) }
SettingKind
(first) titletextrequired
styleoption.normal (default), .primary, .plain, .icon
iconoptionan icon shown before the title
selectedboolshows the button as selected while it holds; stays up to date
StyleLooks like
.normala standard push button
.primarythe window’s main action, in the accent color: pressing Return clicks it, unless the focus is in a text field with its own action
.plainborderless until the pointer is over it, in the accent color, for quieter actions such as those in a row
.iconborderless until the pointer is over it, showing only its icon; the title becomes its tooltip and what screen readers say, so always give one

With several primary buttons on Windows, Return clicks the newest one still showing.

Picking one of a few choices. Give each choice a button whose selected says whether it is the current one, and whose action picks it:

state filter = "all"

row(spacing: 4) {
  button("All", selected: filter == "all") { filter = "all" }
  button("Active", selected: filter == "active") { filter = "active" }
  button("Done", selected: filter == "done") { filter = "done" }
}

Clicking a button never selects it by itself: it shows whatever selected says once its action has run. A .primary button can’t take selected, since it is the window’s default action.

macOSWindows
Selected .normalthe system’s on-off push button, shown onFluent’s checked toggle button, in the accent color
Selected .plain, .iconan on-off accessory bar button, shown ona subtle fill, with the title or icon in the accent color
Screen readersan on-off buttona push-like checkbox, announced as pressed while selected

Block: the action (required). It may change state.


text_field

A text box, bound to a string: one line, or several that wrap and scroll.

text_field($draft, placeholder: "What needs doing?")
text_field($notes, lines: 4)
SettingKind
(first) valuebinding to a stringrequired
placeholderfixed textgrey hint shown while empty; one-line fields only
linesfixed inthow many lines tall, from 1 (default) to 100

A one-line field can take a block, which runs when the user presses Return in it, in place of pressing the window’s .primary button:

text_field($query, placeholder: "Search") { search() }

A field with more than one line wraps its text, scrolls when the text is longer than it is, and takes Return as a new line. Lines in the bound text end in \n on both platforms. Neither platform’s multi-line text box shows a placeholder, so placeholder goes only with one-line fields.

macOSWindows
One linethe system’s text fieldthe system’s edit control
Several linesa text view in a bordered scroll view, as in Mail’s message bodya multi-line edit control with a scroll bar
Tab in several linestypes a tab; Ctrl+Tab moves to the next controlthe same

Block: the action Return runs, for a one-line field; optional. It may change state.


checkbox

A tick box, bound to a bool.

checkbox(checked: $todo.done, title: todo.title)
SettingKind
checkedbinding to a boolrequired
titletextthe label next to the box

Block: none.


switch

Turns a bool on and off, like a checkbox, with the switch at the end of its title, as in a settings window.

switch(on: $notify, title: "Notify me when downloads finish")
SettingKind
onbinding to a boolrequired
titletextshown at the start of the row

The title sits at the start of the row and the switch at its end, so in a column with the default align: .stretch the switches of several rows line up.

macOSWindows
Switchthe system’s NSSwitchFluent’s toggle switch, drawn by Nifty on a real checkbox, so screen readers announce it as a checkbox

Block: none.


slider

Picks a whole number by dragging a knob along a track.

slider($volume, min: 0, max: 10)
SettingKind
(first) valuebinding to an intrequired
minfixed intthe left end; default 0
maxfixed intthe right end; default 100, and no lower than min

The knob slides smoothly and writes the whole number nearest it each time that number changes. A slider has no natural width: it stretches across a column with the default align: .stretch, and is 100 points wide where it can’t stretch.

macOSWindows
Sliderthe system’s NSSliderthe system’s trackbar, drawn by Nifty in Fluent’s style; the arrow keys and Page Up and Page Down move it too

Block: none.


picker

Picks one string from a list, in a pop-up menu.

state speeds = ["Unlimited", "1 MB/s", "500 KB/s"]
state speed = "Unlimited"

picker($speed, options: speeds)
SettingKind
(first) selectionbinding to a stringrequired; the option shown, and where a pick goes
optionslist of textrequired; stays up to date

The picker shows the option equal to selection, or nothing when no option is. It is as wide as its widest option, so it doesn’t change size as the pick changes.

macOSWindows
Pickerthe system’s pop-up buttona button drawn by Nifty as Fluent’s combo box, opening Win32’s own pop-up menu; screen readers announce it as a button named by the option it shows

Win32’s menus follow the system’s light look, also in dark mode, as other classic Windows menus do.

Block: none.


A row of a sidebar list, like those in Finder or File Explorer: an icon, a title, and a badge such as a count at the end. It’s highlighted while selection holds its value, and picking it puts value there.

state section = "inbox"

nav_item("Inbox", value: "inbox", selection: $section, icon: .inbox, badge: "{unread}")
nav_item("Sent", value: "sent", selection: $section, icon: .send)
SettingKind
(first) titletextrequired
valuetextrequired; what selection holds while this item is picked
selectionbinding to a stringrequired; shared by the items of one list
iconoptionan icon, drawn in the accent color
badgetextshown at the end in a quieter color; empty shows nothing

Put nav items one after another in a column(surface: .sidebar); they need no spacing, as each row is the platform’s own height.

macOSWindows
Row28 points, Finder’s sidebar row32 pixels at 100%, Fluent’s navigation item
Selecteda rounded highlight in the system’s selection colora subtle highlight with an accent mark at its start
Under the pointer, presseda fainter highlight, and a stronger one while pressedthe same, in Fluent’s subtle fills

Block: none.


image

Shows the image in a file, as large as fits its frame, keeping its shape.

image("~/Pictures/cover.png", width: 120)
image(item.thumbnail, width: 48, height: 48)
SettingKind
(first) pathtextrequired; where the file is; stays up to date
widthnumbera fixed width; the height follows the image’s shape
heightnumbera fixed height; the width follows the image’s shape

With both width and height, the image fits inside that box, centred, keeping its shape. With neither, it shows at its own size, a pixel to a point.

The path must be a full one, such as /Users/ada/Pictures/a.png or C:\Users\ada\Pictures\a.png, or start with ~ for the home folder. Images that come with an app, found next to its code, aren’t supported yet, so a relative path written directly is an error. A path that names no file, or a file that isn’t an image, shows nothing, at the size given; so does an empty path. The image is read again whenever the path changes.

macOSWindows
Formatswhatever the system reads: PNG, JPEG, GIF, HEIC, TIFF, PDF and morewhat the Windows Imaging Component reads: PNG, JPEG, GIF, BMP, TIFF and ICO, and more with codecs installed
Drawingthe system’s image view, scaling smoothlyscaled smoothly by the Windows Imaging Component and blended with its transparency

Block: none.


progress

A bar that shows how far along some work is. With a value it fills to that percentage; without one it animates to show that work is under way but can’t say how far.

progress(value: item.percent)
progress()
SettingKind
valueinthow far along, from 0 (empty) to 100 (full); stays up to date

A value below 0 shows an empty bar, and one above 100 a full one. Whether a bar has a value is fixed where it’s written; to switch between the two, show one or the other with if.

A progress bar has no natural width: it stretches across a column with the default align: .stretch, and is 100 points wide where it can’t stretch.

macOSWindows
With valuethe system’s bar, in the accent colorthe system’s progress bar
Withoutthe bar’s animated stripethe bar’s marquee, sweeping across it

Block: none.


spacer

Takes up all the leftover space in a row or column, pushing its neighbours apart.

spacer()

No settings. Block: none.


divider

A thin line, to separate groups of elements.

divider()

No settings. Block: none.


context_menu

A menu shown when the user right-clicks the stack it is in, written among the stack’s children.

for item in downloads {
  row() {
    context_menu() {
      menu_item("Download Again") { start(item.url) }
      divider()
      menu("Copy") {
        menu_item("Address") { copied = item.url }
      }
    }
    label(item.url)
  }
}

No settings. It takes no room and draws nothing: it covers its whole stack, padding included, wherever it is written among the children. Where stacks with context menus sit inside each other, a right-click shows the menu of the innermost one under the pointer. A stack has one context_menu, and one can’t be written straight inside a for, where it would cover the stack around the loop once per item; put it in the row or column the loop shows for each item instead.

Its block holds the same entries as a menu: menu_items, divider()s and submenus. The items take no shortcut, since a shortcut can’t say which stack it means, and their actions see the names around the menu, such as a for loop’s item.

macOSWindows
Opens ona right-click or a Control-clicka right-click, or Shift+F10 or the Menu key while a control in the stack has the keyboard, which shows it at the control
Text fields and selectable labelskeep their own menu, with Copy and the likekeep their own menu, with Copy and the like

Block: the entries (required).


drop_target

Takes files and links the user drags onto the stack it is in, from the Finder, File Explorer, a browser or another app, written among the stack’s children.

state dropped: [string] = []

scroll() {
  drop_target($dropped) { download_all() }
  label("Drop links here")
}
SettingKind
(first) droppedbinding to a [string]required; what was dropped, in order: a full path for each file or folder, and the address for each link

Like a context_menu, it takes no room and draws nothing: it covers its whole stack, padding included. Where stacks with drop targets sit inside each other, a drop goes to the innermost one under the pointer. A stack has one drop_target, and one can’t be written straight inside a for.

Each drop replaces what the binding held, then runs the block, so the block sees the new list. While something that can be dropped is dragged over the area, the pointer shows a copy; anything else, such as plain text, isn’t taken.

macOSWindows
Filesfrom the Finder and other apps, as /Users/… pathsfrom File Explorer and other apps, as C:\… paths
Linksdragged from a browser’s address bar or page, as their addressfrom browsers that give a dragged link as its address, as Edge, Chrome and Firefox do
Text fieldsa text field under the pointer takes the drop itself, as texta text field under the pointer takes the drop itself, as text

Block: an action run after each drop (optional). It may change state.


A menu of the app’s menu bar, written at the top level beside its windows, or a submenu, written inside another menu.

menu("Downloads") {
  menu_item("Pause All", shortcut: "shift+p") { pause_all() }
  divider()
  menu("Sort By") {
    menu_item("Name") { sort = "name" }
  }
}
SettingKind
(first) titlefixed textrequired

The block holds the menu’s entries, in order: menu_items, divider()s, which draw a line between groups of items, and submenus. Menus can’t hold if or for; an item’s action decides what to do when it runs.

macOSWindows
Wherethe menu bar at the top of the screen: the app menu with Quit, your first menu, the Edit menu with Undo, Cut, Copy, Paste and Select All, then the rest of your menusthe menu bar of every window, holding your menus in order; text fields take their editing shortcuts themselves
Lookthe system’s ownWin32’s own, which stays light in dark mode, as classic Windows menu bars do

Block: the entries (required).


An item of a menu, which runs its block when the user chooses it or presses its shortcut.

menu_item("New Download", shortcut: "n") { new_download() }
SettingKind
(first) titlefixed textrequired
shortcutfixed texta key, after shift+ or alt+ if any, such as "n", "shift+n" or "alt+return"

A shortcut always uses the platform’s command key: Cmd on macOS, Ctrl on Windows. Keys are the letters, the digits, , . / ; ' [ ] - =, and return, delete, up, down, left and right; alt is Option on macOS. The shortcuts the system’s own commands use, q (Quit), z and shift+z (Undo and Redo), x, c, v and a (Cut, Copy, Paste and Select All), are kept for them, and two items can’t share a shortcut.

The shortcut shows beside the item’s title, as each platform writes it: ⇧⌘N on macOS, Ctrl+Shift+N on Windows. It works whenever one of the app’s windows has the keyboard. On Windows, a punctuation key that the user’s keyboard layout doesn’t have gives no shortcut, though the item still shows it.

Block: the action (required). It may change state.


Icons

button and nav_item take icon: with one of these:

.plus, .trash, .star, .star_fill, .check, .check_circle, .circle, .home, .inbox, .archive, .list, .calendar, .sun, .flag, .close, .gear, .search, .mail, .send, .folder, .clock, .tag, .person, .document, .download, .heart, .bell, .pencil, .share, .info

They come from the system’s own icon set: SF Symbols on macOS, and Segoe Fluent Icons on Windows 11 (Segoe MDL2 Assets on Windows 10). Nothing is added to your app, and icons follow the system’s look and the user’s text size. On Windows, .inbox shows the same envelope as .mail, as the Windows icon fonts have no inbox tray.


Where this comes from: the list of elements is defined once in crates/nifty-check/src/elements.rs. The checker and the editor’s autocomplete both read it, so they always agree.