Skip to Content
Widget catalog

Widget catalog

Start with the job the interface must do. Every published name below links to its generated signature. Match that reference to the version in your pubspec.lock.

TextInput has a curated page with its properties, key handling, and ownership rules.

Layout and composition

NeedStart withImportant distinction
Horizontal or vertical childrenRow, Column, Flexspacing inserts whole cells between children
One child owns leftover spaceExpanded, FlexibleExpanded fills its share; Flexible may use less
Padding, alignment, or exact extentPadding, Align, SizedBoxKeep each relationship in the widget that owns it
Minimum or maximum sizeConstrainedBoxA constraint is a range, not a promised size
Themed titled regionPanelShrink-wraps content; width and height set exact extents
Fill, border, and child compositionContainer, DecoratedBoxUse Container for composition; DecoratedBox only paints
Overlapping childrenStack, PositionedPositions are integer cell offsets
Children that continue onto another runWrapUse Row when wrapping would be surprising
A child that must read its own spaceLayoutBuilderBuilds during layout from the offered BoxConstraints

LayoutBuilder reports the space a parent offers. It is available in Noir 0.0.1; see Installation.

Read Layout in terminal cells before you fix widths across a screen. Terminal proportions make one row of spacing visually larger than one column.

Text, status, and feedback

NeedWidgetNotes
Plain or styled textText, RichText, TextSpanWidth follows terminal graphemes, not Dart string length
Large glyph headingAsciiFontUses Noir’s own terminal-safe font families
Compact state labelBadgeVariants provide semantic defaults through Theme
Completion fractionProgressBarWidth is explicit and resolves to eighth-cell steps
Ongoing workSpinnerRemove it when work ends; use SpinnerFrames.line for ASCII-only output
Section ruleDividerPrefer one structural divider to borders around every child
Shared visual valuesThemeRead values with Theme.of(context)

Use a ProgressBar for measurable work and a Spinner when progress is unknown:

Row( spacing: 1, children: [ if (totalBytes == null) const Spinner(frames: SpinnerFrames.line) else ProgressBar(value: downloadedBytes / totalBytes, width: 24), Text(status), ], )

Input and selection

NeedWidgetInteraction model
Activate one commandButtonEnter, Space, or primary pointer activation
Boolean valueCheckbox, SwitchUse the label that best describes the resulting state
Value on a rangeSliderArrow-key and pointer adjustment
One-line editingTextInputEnter submits; default maxLength is 100
Attached suggestionsAutocomplete<T>Caller controls options, status, selection, and async work
Multi-line editingTextAreaFixed by default; opt into wrapping, bounded growth, or Enter-to-submit
Vertical option listSelectArrows or j/k change highlight; Enter confirms
Horizontal section switcherTabSelectFixed-width tabs by default; tabWidth: null sizes to labels
Local raw key ownershipFocus, FocusScopeReturn ignored for keys the region does not own
Semantic key commandsShortcuts, ActionsMap keys to Intents, then Intents to application commands
Pointer hit regionPointerListenerRead MouseEvent.localPosition after hit testing

PointerListener.mouseCursor selects a custom mouse shape. Interactive controls provide hover pointers automatically. See Show a hand over an action for requirements and behavior.

Select and TabSelect share typed options but express different spatial models:

Select<String>( options: const [ SelectOption(name: 'Development', value: 'dev'), SelectOption(name: 'Production', value: 'prod'), ], height: 2, autofocus: true, onSelect: (_, option) { final environment = option.value; if (environment != null) applyEnvironment(environment); }, )

Select.height is visible rows and defaults to 8. Set it to the option count for a short list. SelectOption.value is nullable.

Autocomplete<T> keeps request policy outside the widget. Supply an explicit AutocompleteStatus and options from the application. Tab enters a ready list, Enter or a primary click selects, and Escape dismisses the attached surface while idle Escape stays available to an ancestor.

See Handle input and focus for event order, controller ownership, and focus traversal.

Scrolling and data

NeedWidgetChoose it when…
One oversized childScrollBoxThe content already exists as one widget subtree; tail follow is opt-in
Repeated visible rowsListViewRows should be built only for the visible window
Hierarchical rowsTreeViewStable caller IDs need expansion and keyboard navigation
Interactive datasetDataTableRows need aligned columns, selection, and caller-owned sorting
Finite rich-text gridTextTableCells need wrapping, borders, and row-major text selection

DataTable virtualizes its body and reports sort requests; your data owner does the reordering. TextTable lays out a finite matrix of InlineSpan cells and supports document-style selection. They solve different table problems.

TreeViewController owns generic roots, expansion, and selection; path, filesystem, or other domain behavior stays in the application. TreeView uses Up and Down for visible selection, Right and Left for hierarchy navigation, and one Enter or primary-click activation route.

Overlays and menus

NeedWidgetBoundary
Custom transient contentOverlayPortalCaller owns placement, input, focus, and dismissal
Centered blocking interactionModalClosed focus loop and pointer barrier; caller supplies chrome
Commands anchored to a launcherMenuAnchorFollows launcher geometry and owns outside-click dismissal

Modal keeps its ordinary child mounted, centers a fresh modalBuilder subtree while open, loops Tab and Shift+Tab over live modal controls, restores the captured focus node on close, and blocks every outside pointer event. Escape dismisses by default; outside primary-button dismissal is opt-in. Compose a Panel inside the builder, because Modal deliberately has no visual style of its own.

final modal = ModalController(); Modal( controller: modal, initialFocusNode: cancelFocus, modalBuilder: (context) => Panel( title: 'Discard changes?', child: Row( spacing: 1, children: [ Button( label: 'Cancel', focusNode: cancelFocus, onPressed: modal.close, ), Button( label: 'Discard', onPressed: () { discardChanges(); modal.close(); }, ), ], ), ), child: Button(label: 'Open', onPressed: modal.open), )

One ModalController attaches to one live Modal. open() throws while detached, close() is a detached no-op, and replacing the controller preserves an open subtree without transition callbacks. Calling open() again promotes that modal and reestablishes focus inside it. When a top modal closes, the modal revealed below preserves a valid restored focus or repairs to an available descendant.

Noir has one framework-owned root overlay. It does not expose public OverlayEntry, nested overlays, transforms, linked layers, menu cascades, or overlay animation APIs.

Documents and media

NeedWidget or typeBoundary
Selectable sourceCodeViewPlain text by default; supply a CodeHighlighter for syntax ranges
Unified or split patchDiffView, UnifiedDiffParserCaller chooses view mode and owns any source action
GitHub-flavoured MarkdownMarkdownViewStandalone mode scrolls; embedded mode delegates scrolling to a parent
Terminal imageImage, TerminalImageFile, network, encoded bytes, or RGBA; protocol support depends on the terminal
Semantic terminal linklinked TextSpanThe terminal decides whether and how the link is presented
Explicit selected-text copySelectedText and document callbacksOSC52 acceptance depends on terminal policy

For a read-only DiffView embedded in a decision surface, set canRequestFocus: false so the enclosing modal keeps keyboard traversal.

A standalone Markdown document owns focus, selection, and its scrolling viewport:

MarkdownView( markdown: releaseNotes, autofocus: true, )

Check Platform support before you depend on terminal images, semantic links, or OSC52 clipboard behavior.

State and motion

StatefulWidget and its State are the normal retained state boundary. ValueNotifier and ChangeNotifier separate testable state from one widget. AnimationController with TickerProviderStateMixin or SingleTickerProviderStateMixin drives time-based values. ThemeData carries the shared visual values.

The optional companion package noir_signals offers the same ownership model through composable hooks. Read State, identity, and ownership to choose, and Manage state and resources when one lifecycle concern should be reusable.

Last updated on