RiseUI
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')],
)