Tinyrack

Select

Choose one typed Flutter value with controlled, uncontrolled, and FormField APIs.

Choose one typed value with controlled, uncontrolled, and FormField APIs while retaining Material keyboard and search behavior.

Contract

AxisContract
Open statePointer hover, open, and selected states use background emphasis only. The focus border appears only for keyboard-origin focus.
CompositionPass leading content to identify the trigger without changing the typed value. Add a TRSelectItem description for supporting text that stays in the option row; the selected trigger keeps only the label, and default search matches both strings. Give each item a key when tests or state restoration must identify it across layer and sheet presentations.
Adaptive geometryChoose TRSelectPresentation.layer or TRSelectPresentation.sheet from the product layout policy. An anchored layer keeps its full-list intrinsic size, while a sheet keeps its full-list height and continues tracking viewport width; explicit fixed dimensions win. The search field sits outside the single options viewport, and wheel, trackpad, or drag input over it does not scroll the list or move the sheet.
Layer sizeTRLayerSize applies to the complete anchored layer. Width can follow content, use a fixed value, match the anchor, or stay at least as wide as the anchor; height can follow content or use a fixed value. Optional bounds apply before the safe viewport clamps the result.

Install

Add the package, then import its public library.

flutter pub add tinyrack_ui
import 'package:tinyrack_ui/tinyrack_ui.dart';

Playground

Usage

import 'package:material_ui/material_ui.dart';
import 'package:tinyrack_ui/tinyrack_ui.dart';

TRSelect<String>.controlled(
  value: channel,
  items: const [
    TRSelectItem(value: 'stable', label: 'Stable'),
    TRSelectItem(value: 'beta', label: 'Beta'),
  ],
  onValueChange: setChannel,
)

Examples

Controlled value permalink

Use the named controlled constructor when the parent owns the value. Leading content identifies the trigger, while item keys identify the same option in layer and sheet presentations. A null value explicitly clears the selection.

Form validation permalink

TRSelectFormField participates in validation, save, reset, autovalidation, and state restoration without replacing Material keyboard behavior.

Searchable options permalink

Set searchable when the list is long enough that reading it is slower than typing. A filter field opens above the options and takes focus, arrow down moves into the first enabled match, and Enter commits a match once only one is left. Pass filter to match on something other than the label.

Product-owned presentation permalink

Resolve presentation from the product breakpoint instead of asking Select to infer responsiveness. `width` and `height` describe the complete popup, including the fixed search header, and default independently, so stating one keeps the other. The chosen presentation stays active through a resize until the Select closes.

API

Presentation and control

PropType / defaultPurpose
presentationTRSelectPresentation · const TRSelectPresentation.layer()Chooses the caller-owned presentation for the next open. The opened value is retained until close.
TRSelectPresentation.layerwidth, height, placement = bottomStart, useRootOverlay = trueOpens the shared panel in a collision-aware layer. width and height default independently, so setting one keeps the other: the default is at least the anchor and TRMeasurements.measureMd wide, lets content grow through TRMeasurements.overlayWidthSm, and caps content height at TRMeasurements.measureXl.
TRSelectPresentation.sheetmaxExtent = 1, snapPoints = const [], showDragHandle = trueOpens the same panel in a modal bottom sheet. The options viewport owns content gestures, while only the handle moves the sheet.
controllerTRSelectController? · nullReads isOpen and calls open, close, or toggle without depending on the selected presentation.
TRSelectFormFieldFormField<T>Forwards presentation and controller while retaining typed validation, saving, and reset behavior.
paddingTRFieldPadding · TRFieldPadding.standardChooses whether the trigger adds the inline inset its size scale defines. Use TRFieldPadding.none where a row or toolbar already supplies the inset, so the value and its chevron reach the same rail as the controls beside them. The height, and so the hit target, is unchanged.

Shared layer sizing

PropType / defaultPurpose
TRLayerSizewidth = TRLayerWidth.content(), height = TRLayerHeight.content()Combines width and height policies for the complete layer, including border and padding.
TRLayerWidth.content{double? min, double? max}Lets content choose the width within optional bounds.
TRLayerWidth.fixeddouble valueRequests an exact width before the safe viewport clamp.
TRLayerWidth.matchAnchor{double? min, double? max}Uses the anchor width, clamped to the optional bounds.
TRLayerWidth.atLeastAnchor{double? min, double? max}Lets content grow from at least the anchor width. An anchor wider than max still wins unless the safe viewport is smaller.
TRLayerHeight.content{double? min, double? max}Lets content choose the height within optional bounds.
TRLayerHeight.fixeddouble valueRequests an exact height before the safe viewport clamp.