Components
InputGroup
One shared border/shadow with prefix and suffix slots; RiseInput children are borderless slots — aligned with HeroUI InputGroup (input-group.tsx + input-group.css).
Usage
Default: icon prefix; focus shifts border only.
RiseInputGroup(
prefix: Icon(Icons.email_outlined),
children: const [RiseInput(placeholder: 'name@email.com')],
)Text prefix
Plain text affix (e.g. https://).
const RiseInputGroup(
prefix: Text('https://'),
children: [RiseInput(placeholder: 'example.com')],
)Text suffix
Trailing text (e.g. .com).
const RiseInputGroup(
children: [RiseInput(placeholder: 'subdomain')],
suffix: Text('.com'),
)Icon prefix + text suffix
Mixed affix types.
RiseInputGroup(
prefix: Icon(Icons.currency_exchange),
suffix: const Text('USD'),
children: const [RiseInput(placeholder: '0.00')],
)Copy button suffix
IconButton + Clipboard.
RiseInputGroup(
children: [RiseInput(controller: controller)],
suffix: IconButton(
icon: const Icon(Icons.copy_outlined),
onPressed: () => Clipboard.setData(ClipboardData(text: controller.text)),
),
)Icon prefix + copy
Globe + copy action.
RiseInputGroup(
prefix: Icon(Icons.language_outlined),
children: [RiseInput(controller: controller)],
suffix: IconButton(
icon: const Icon(Icons.copy_outlined),
onPressed: () => Clipboard.setData(ClipboardData(text: controller.text)),
),
)Loading state
Spinner or trigger in suffix slot.
RiseInputGroup(
children: const [RiseInput(placeholder: 'Search…')],
suffix: busy
? RiseSpinner(size: RiseSpinnerSize.sm)
: IconButton(icon: Icon(Icons.refresh_outlined), onPressed: onRefresh),
)Keyboard hint
Suffix displays a shortcut affordance (⌘K).
RiseInputGroup(
children: const [RiseInput(placeholder: 'Quick command…')],
suffix: Padding(
padding: EdgeInsets.only(right: 8),
child: /* ⌘K kbd pill */,
),
)Badge suffix
[RiseChip] as trailing badge.
RiseInputGroup(
prefix: Icon(Icons.code_outlined),
children: const [RiseInput(placeholder: 'feature/api-v2')],
suffix: RiseChip(size: RiseChipSize.sm, child: Text('Beta')),
)Required field
[RiseLabel] with isRequired + group.
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RiseLabel.text('Project name', isRequired: true),
RiseInputGroup(
prefix: Icon(Icons.folder_outlined),
children: [RiseInput(placeholder: 'my-app')],
),
],
)Validation
isInvalid + [RiseFieldError] message.
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RiseInputGroup(
isInvalid: invalid,
children: [RiseInput(controller: c, onChanged: (_) => setState(() {}))],
),
RiseFieldError(visible: invalid, child: Text('Use at least 3 characters.')),
],
)Disabled
enabled: false on the group.
const RiseInputGroup(
enabled: false,
prefix: Icon(Icons.lock_outline),
children: [RiseInput(placeholder: 'Read-only token')],
)Icon suffix only
Trailing icon slot.
RiseInputGroup(
suffix: Icon(Icons.alternate_email),
children: const [RiseInput(placeholder: 'Email address')],
)Currency affixes
$ … USD.
const RiseInputGroup(
prefix: Text('\$'),
suffix: Text('USD'),
children: [RiseInput(placeholder: 'Set a price')],
)Password toggle
Visibility in suffix.
RiseInputGroup(
suffix: IconButton(
onPressed: () => setState(() => obscure = !obscure),
icon: Icon(obscure ? Icons.visibility_off : Icons.visibility),
),
children: [RiseInput(obscureText: obscure, placeholder: 'Password')],
)Variants
Primary vs secondary chrome.
const RiseInputGroup(
variant: RiseInputGroupVariant.secondary,
prefix: Icon(Icons.email_outlined),
children: [RiseInput(placeholder: 'Secondary variant')],
)Full width
Stacked email + password rows.
const RiseInputGroup(
fullWidth: true,
prefix: Icon(Icons.email_outlined),
children: [RiseInput(placeholder: 'name@email.com')],
)Invalid chrome
Static isInvalid styling.
const RiseInputGroup(
isInvalid: true,
prefix: Icon(Icons.email_outlined),
children: [RiseInput(placeholder: 'Invalid email')],
)