Skip to content

Inputs ​

CDX UI ships purpose-built input controls for the cases Material's defaults don't cover well: a labeled slider, a live-formatting rich text field, and a consistent form-field label primitive.

Live examples ​

Open the full Inputs example or the focused Rich Text example.

CdxSlider ​

Canonical labeled slider for the design system. Wraps Material's Slider with a primary-coloured value label, optional leading icon, and configurable min/max end labels — replaces the ad-hoc "duration / days / count" sliders that each rolled their own chrome.

dart
// Top-centered value with calendar icon, days formatting
CdxSlider(
  value: days.toDouble(),
  min: 0,
  max: 365,
  onChanged: (v) => setDays(v.round()),
  format: (v) => '${v.round()} days',
  icon: FluentIcons.calendar_24_regular,
)

// Compact row: trailing value, no end labels
CdxSlider(
  value: rows.toDouble(),
  min: 10, max: 30, divisions: 2,
  onChanged: (v) => setRows(v.toInt()),
  format: (v) => v.toInt().toString(),
  labelPosition: CdxSliderLabelPosition.trailing,
  showEndLabels: false,
)

Parameters ​

ParamTypeDefaultNotes
valuedoublerequired
min / maxdoublerequired
divisionsint?null
onChangedValueChanged<double>required
onChangeEndValueChanged<double>?nullForwarded to Slider.onChangeEnd
formatString Function(double)requiredDrives the prominent label + Material drag tooltip
endLabelFormatString Function(double)?falls back to formatMin/max end labels
iconIconData?nullLeading icon next to the value label
labelPositionCdxSliderLabelPositiontoptop / trailing / none
showEndLabelsbooltrueAuto-suppressed on right when labelPosition is trailing
sizeCdxControlSizestandardcompact gives a smaller value label + icon for tight surfaces
enabledbooltrueFalse mutes the whole slider and ignores input

CdxRangeSlider ​

CdxSlider with two thumbs, in the same chrome and under the same slider theme, so the two look alike. Picks a RangeValues between min and max; the prominent label reads start – end.

dart
CdxRangeSlider(
  values: RangeValues(start.toDouble(), end.toDouble()),
  max: 12,
  divisions: 12,
  minGap: 1,
  format: (v) => '${v.round()}',
  labelPosition: CdxSliderLabelPosition.none,
  showEndLabels: false,
  onChanged: (range) => setRange(range.start.round(), range.end.round()),
)
  • Gap. The thumbs never come closer than minGap: the thumb that moves stops at the gap. An unchanged range is not reported.
  • Keyboard. Each thumb is a focus stop; the arrow keys step it by one division (or a small step without divisions). Screen readers hear each thumb's value through format.
  • Long labels. On both sliders the labels give way to the track rather than overflow: an end label takes at most a quarter of the row, a trailing value label two fifths, and the value label above the track the row's width, each ending in an ellipsis past that.
ParamTypeDefaultNotes
valuesRangeValuesrequiredstart <= end, within min..max
min / maxdouble0 / 1
divisionsint?nullSnaps both thumbs to steps
minGapdouble0Smallest end - start
onChangedValueChanged<RangeValues>requiredDrag and keyboard
onChangeEndValueChanged<RangeValues>?nullForwarded to RangeSlider.onChangeEnd
formatString Function(double)requiredOne thumb's value: label, drag tooltip, semantics
endLabelFormat, icon, labelPosition, showEndLabels, size, enabledAs on CdxSlider

RemarksField ​

Rich-text input with live inline formatting. As the user types markers like **bold** or ==highlight==, they render formatted in-place. When the cursor leaves a span, the markers hide (MarkerVisibility.whenActive).

dart
RemarksField(
  controller: _remarksController,
  labelText: 'Comments',
  isRequired: true,
  hintText: 'Why was this rejected?',
  helperText: 'Visible to approvers and the submitter.',
  maxLines: 4,
  showToolbar: true,
  errorText: form.errors['comments'],
)

Parameters ​

ParamTypeDefault
controllerTextEditingController?null (creates internal TextfEditingController)
labelTextString?null
isRequiredboolfalse (adds red * next to label)
hintText / helperText / errorTextString?null
maxLinesint4
maxLengthint?null
showToolbarbooltrue
prefixIconWidget?null
autofocus / focusNode / onChangedstandard

When the caller passes a plain TextEditingController, RemarksField syncs back to it whenever the internal TextfEditingController changes — so existing form-binding patterns work unchanged.

RemarksText ​

Read-only renderer for textf-formatted strings. Drop-in replacement for Text that parses the same markup as RemarksField:

dart
RemarksText(
  text: comment.body,
  maxLines: 5,
  overflow: TextOverflow.ellipsis,
)

Markup ​

MarkupRendered
**bold** / __bold__bold
*italic* / _italic_italic
~~strikethrough~~strike
++underline++underline
==highlight==highlight (tertiary container background)
`code`inline code (monospace + surfaceContainerHighest)
^superscript^superscript
~subscript~subscript

FormattingToolbar ​

The bare toolbar embedded inside RemarksField. Use it directly when building a custom rich-text container.

dart
FormattingToolbar(
  controller: myTextfController,
  focusNode: myFocusNode,
)

Renders a row of icon buttons for bold / italic / highlight / underline / strikethrough / code / superscript / subscript. Each wraps the current selection (or inserts empty markers at the cursor).

CdxFieldLabel ​

Standard form-field label — mirrors the fieldLabel(context) extension in vyuh_feature_forms. Use it for non-form widgets that still need to present the same visual contract (label in labelMedium, required * in error color, focus / error coloring).

dart
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
  CdxFieldLabel(
    label: 'Assignee',
    required: true,
    hasFocus: _hasFocus,
    hasError: _hasError,
    trailing: Button.text(label: 'Create new', onPressed: _createAssignee),
  ),
  MyCustomInput(...),
])
ParamDefault
requiredfalse (red * next to label)
hasFocusfalse (drives label color to primary)
hasErrorfalse (drives label color to error)
paddingEdgeInsets.only(bottom: 6)
trailingnull (optional same-row affordance such as help or "Create new")

CdxCodeEditor ​

A code box for editing code or, with readOnly: true, for showing code to select and copy. There is one widget for both. It is built on re_editor: it lays out and colours only the lines it paints, so thousands of lines type within a frame.

Web builds need an override

Every app that builds for the web adds dependency_overrides: isolate_manager: ^6.3.2 to its pubspec (or its workspace root's). re_editor's default isolate_manager 4.x breaks flutter build web --wasm; see the vyuh_cdx_ui README.

dart
CdxCodeEditor(
  language: CdxCodeLanguage.json,
  value: prettyJson,           // or controller: CdxCodeController()
  onChanged: (text) => ...,    // edits only
  lineNumbers: true,
  minLines: 8,
  maxLines: 20,                // or expands: true in a bounded parent
  error: parseError,           // CdxCodeError, underlined at its line
)
  • Languages. CdxCodeLanguage.json colours keys, strings, numbers and true/false/null, and folds {} and [] blocks. CdxCodeLanguage.yaml colours keys, values, literals and comments, and folds the lines indented under a line. CdxCodeLanguage.plain colours and folds nothing. Another language is CdxCodeLanguage(name:, tokenizer:, folding:) with a CdxCodeTokenizer that splits one line into CdxCodeTokens and, if its blocks fold, a CodeChunkAnalyzer: CdxBracketChunkAnalyzer for bracketed blocks, CdxIndentChunkAnalyzer for indented ones, or your own that knows its strings and comments. Keep a language in a const or a field.
  • Folding. On by default (folding: true) for a language that folds. A chevron in the gutter beside a block's first line folds it, pointing down while the block shows and to the side once it is folded; a tap on it again, or on the ⋯ after the folded line, unfolds it. Hovering a chevron names the action and its shortcut. Ctrl+Alt+[ folds the block at the caret and Ctrl+Alt+] unfolds it (Command+Option on Apple). Folding hides lines and never changes the text: text, onChanged, the line numbers and an error's line all count the folded lines, and a fold stays through edits outside it. folding: false turns it off; a document longer than CdxCodeEditor.foldingLineLimit lines does not fold.
  • Theme. Token colours come from the ColorScheme and CdxConfig, so they follow light and dark. The outline, radius, padding and focus ring follow CdxConfig (Classic or Blue); framed: false leaves the frame to a parent that already draws one. Code draws in CdxFonts.mono.
  • Keys. Tab indents to the next two-space stop and Shift+Tab outdents. Ctrl+Tab and Ctrl+Shift+Tab move focus out; in a read-only box Tab moves focus as usual.
  • Errors. CdxCodeError(message, line:, column:) counts from 1. CdxCodeError.fromFormatException(e, source) places a jsonDecode failure; CdxCodeError.at(source, offset, message) places any offset. The editor underlines the token, marks the line in the gutter with the message as a tooltip, and shows the message under the code.
ParamDefault
languagerequired (CdxCodeLanguage.json, .yaml, .plain, or your own)
controller / valueone of them; value keeps the text in the box
readOnlyfalse
lineNumbersfalse
foldingtrue (folds when the language has blocks)
minLines / maxLines / expands1 / null (grows) / false
errornull
placeholder, style, framed, autofocus, focusNode—

CdxFileDropZone ​

An area that takes files dropped on it. It is generic in the host's file type and calls onDropped with the files it accepts. While a drag is over it only the outline changes, from a dashed hairline to a solid primary line; the fill never does.

dart
CdxFileDropZone<XFile>(
  accept: (file) => file.name.endsWith('.pdf'),
  multiple: false,
  onDropped: (files) => controller.upload(files),
  onRejected: (files) => toast('Only one PDF'),
  onBrowse: () async => controller.upload(await pickFiles()),
  dropTarget: (context, target, child) => DropTarget( // desktop_drop
    onDragEntered: (_) => target.enter(),
    onDragExited: (_) => target.exit(),
    onDragDone: (detail) => target.drop(detail.files),
    child: child,
  ),
)

Flutter cannot take files from the operating system without a platform plugin, and vyuh_cdx_ui carries none. The host wires its own plugin through dropTarget, calling CdxFileDropTarget.enter, exit and drop as the plugin reports the drag. A Draggable in the app whose data is a file or a list of files drops on the zone without any wiring. With onBrowse the zone is also a button (tap, Enter or Space) for the host's file picker. Its words come from CdxStrings.dropFiles and dropFilesOrBrowse, or label and hint.

CdxFieldAffixButton ​

An icon button set into a field's prefixIcon or suffixIcon, the field's height so it never grows the field. shape says how its hover and focus fill meets the field:

CdxFieldAffixShapeFill
round (default)A round fill on a field-height square
flatA square fill the field's height inside the outline, for a button beside another control at the field's end
fieldEndFlat where it meets the text, the field's corners at its edge, inset by the outline so it never paints over it
dart
CdxTextField(
  controller: date,
  suffixIcon: CdxFieldAffixButton(
    icon: CdxIcons.calendar,
    tooltip: 'Pick a date',
    shape: CdxFieldAffixShape.fieldEnd,
    onPressed: openCalendar,
  ),
)

The icon keeps its enabled colour without onPressed: a disabled field mutes itself and its affixes as one unit.

CdxFieldStepper ​

A number field's stepper arrows: an up half above a down half, behind a hairline divider, set into a field's suffixIcon. CdxNumberField sets one in; a field of another kind that steps a value sets one into its CdxTextField the same way.

dart
CdxTextField(
  controller: pageText,
  suffixIcon: CdxFieldStepper(
    incrementTooltip: 'Next page',
    decrementTooltip: 'Previous page',
    onIncrement: page < last ? () => goTo(page + 1) : null,
    onDecrement: page > 1 ? () => goTo(page - 1) : null,
  ),
)

The column fills the field's height one outline in and sits at the field's end: the right in left-to-right text, the left in right-to-left. A half with a null callback is inert and mutes its chevron; a disabled field (enabled: false) mutes the stepper with itself as one unit. Each half is a focus stop and a button named by its tooltip. size matches the field's; incrementIcon and decrementIcon default to the chevrons. reserveRoom (default true) pads the stepper to the field-height end slot so the column reaches the edge; set it false when a unit label shares the field's end.

CdxTextField inline variant ​

CdxTextFieldVariant.inline edits words where they stand, such as a heading on a canvas. It draws no outline, fill, padding or minimum height in any state, uses no strut, and sets its text in the caller's style over the ambient DefaultTextStyle, as a Text does. A Text swapped for the field leaves every glyph in place. It takes no label, helper, error, unit, affixes or maxLength: the host frames the words.

dart
CdxTextField(
  controller: controller,
  focusNode: focus,          // listen to it to stop editing on focus loss
  style: headingStyle,       // the same style the Text used
  textAlign: TextAlign.start,
  maxLines: null,            // grows with its lines
  variant: CdxTextFieldVariant.inline,
  onChanged: write,
)
  • Pickers — DateTimePicker is also an input field
  • Menus — CdxMenu behind CdxMenuTrigger.field is a select field
  • Theming — input theme is part of createCdxTheme