Inputs
Ported from HyperUI’s Inputs — see Attribution.
Includes a dark: variant — toggle the site theme (top right) to preview it.
class Inputs1Example extends StatelessWidget { const Inputs1Example({super.key});
@override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; final isSmUp = MediaQuery.sizeOf(context).width >= 640; final fontSize = isSmUp ? 14.0 : 16.0; final lineHeight = isSmUp ? 20.0 : 24.0; final baseStyle = Theme.of(context).textTheme.bodyLarge ?? const TextStyle();
return Style( 'mx-auto max-w-sm p-6', children: [ const Style( 'text-sm font-medium text-gray-700 dark:text-gray-200', display: DisplayType.inline, child: Text('Email'), ), Style( 'mt-0.5 w-full rounded border-gray-300 shadow-sm sm:text-sm dark:border-gray-600 dark:bg-gray-900 dark:text-white', child: TextFormField( keyboardType: TextInputType.emailAddress, style: baseStyle.copyWith( fontSize: fontSize, height: lineHeight / fontSize, color: isDark ? Colors.white : Colors.black, ), ), ), ], ); }}<label for="Email"> <span class="text-sm font-medium text-gray-700"> Email </span>
<input type="email" id="Email" class="mt-0.5 w-full rounded border-gray-300 shadow-sm sm:text-sm" /></label>Base with icon
Section titled “Base with icon”class CurrentColorIcon extends StatelessWidget implements StyleReplacedElementWrapper { const CurrentColorIcon(this.svg, {super.key});
final String svg;
@override Widget build(BuildContext context) { final color = IconTheme.of(context).color ?? const Color(0xFF000000); return SvgPicture.string(svg, theme: SvgTheme(currentColor: color)); }}
const _atSvg = '''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 12a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Zm0 0c0 1.657 1.007 3 2.25 3S21 13.657 21 12a9 9 0 1 0-2.636 6.364M16.5 12V8.25"/></svg>''';
class Inputs2Example extends StatelessWidget { const Inputs2Example({super.key});
@override Widget build(BuildContext context) { final isSmUp = MediaQuery.sizeOf(context).width >= 640; final fontSize = isSmUp ? 14.0 : 16.0; final lineHeight = isSmUp ? 20.0 : 24.0; final baseStyle = Theme.of(context).textTheme.bodyLarge ?? const TextStyle();
return Style( 'mx-auto max-w-sm p-6', children: [ const Style( 'text-sm font-medium text-gray-700', display: DisplayType.inline, child: Text('Email'), ), Style( 'relative', children: [ Style( 'mt-0.5 w-full rounded border-gray-300 pe-8 shadow-sm sm:text-sm', child: TextFormField( keyboardType: TextInputType.emailAddress, style: baseStyle.copyWith( fontSize: fontSize, height: lineHeight / fontSize, color: Colors.black, ), ), ), Style( 'absolute inset-y-0 right-0 grid w-8 place-content-center text-gray-700', child: Style('size-4', child: CurrentColorIcon(_atSvg)), ), ], ), ], ); }}<label for="Email"> <span class="text-sm font-medium text-gray-700"> Email </span>
<div class="relative"> <input type="email" id="Email" class="mt-0.5 w-full rounded border-gray-300 pe-8 shadow-sm sm:text-sm" />
<span class="absolute inset-y-0 right-0 grid w-8 place-content-center text-gray-700"> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4" > <path stroke-linecap="round" stroke-linejoin="round" d="M16.5 12a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Zm0 0c0 1.657 1.007 3 2.25 3S21 13.657 21 12a9 9 0 1 0-2.636 6.364M16.5 12V8.25" /> </svg> </span> </div></label>Base with button
Section titled “Base with button”class CurrentColorIcon extends StatelessWidget implements StyleReplacedElementWrapper { const CurrentColorIcon(this.svg, {super.key});
final String svg;
@override Widget build(BuildContext context) { final color = IconTheme.of(context).color ?? const Color(0xFF000000); return SvgPicture.string(svg, theme: SvgTheme(currentColor: color)); }}
const _searchSvg = '''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"/></svg>''';
class Inputs3Example extends StatelessWidget { const Inputs3Example({super.key});
@override Widget build(BuildContext context) { final isSmUp = MediaQuery.sizeOf(context).width >= 640; final fontSize = isSmUp ? 14.0 : 16.0; final lineHeight = isSmUp ? 20.0 : 24.0; final baseStyle = Theme.of(context).textTheme.bodyLarge ?? const TextStyle();
return Style( 'mx-auto max-w-sm p-6', children: [ const Style( 'text-sm font-medium text-gray-700', display: DisplayType.inline, child: Text('Search'), ), Style( 'relative', children: [ Style( 'mt-0.5 w-full rounded border-gray-300 pe-10 shadow-sm sm:text-sm', child: TextFormField( style: baseStyle.copyWith( fontSize: fontSize, height: lineHeight / fontSize, color: Colors.black, ), ), ), Style( 'absolute inset-y-0 right-2 grid w-8 place-content-center', child: Style( 'rounded-full p-1.5 text-gray-700 transition-colors hover:bg-gray-100', child: Style('size-4', child: CurrentColorIcon(_searchSvg)), ), ), ], ), ], ); }}<label for="Search"> <span class="text-sm font-medium text-gray-700"> Search </span>
<div class="relative"> <input type="text" id="Search" class="mt-0.5 w-full rounded border-gray-300 pe-10 shadow-sm sm:text-sm" />
<span class="absolute inset-y-0 right-2 grid w-8 place-content-center"> <button type="button" aria-label="Submit" class="rounded-full p-1.5 text-gray-700 transition-colors hover:bg-gray-100" > <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4" > <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" /> </svg> </button> </span> </div></label>Floating label
Section titled “Floating label”Includes a dark: variant — toggle the site theme (top right) to preview it.
class Inputs4Example extends StatelessWidget { const Inputs4Example({super.key});
@override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; final isSmUp = MediaQuery.sizeOf(context).width >= 640; final fontSize = isSmUp ? 14.0 : 16.0; final lineHeight = isSmUp ? 20.0 : 24.0; final baseStyle = Theme.of(context).textTheme.bodyLarge ?? const TextStyle(); final borderColor = isDark ? tailwindColors['gray']![600]! : tailwindColors['gray']![300]!; final labelStyle = TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: isDark ? Colors.white : tailwindColors['gray']![700], ); final border = OutlineInputBorder( borderRadius: const BorderRadius.all(Radius.circular(4)), borderSide: BorderSide(color: borderColor), );
return Style( 'mx-auto max-w-sm p-6', child: Style( 'relative', children: [ Style( 'peer mt-0.5 w-full rounded border-gray-300 shadow-sm sm:text-sm dark:border-gray-600 dark:bg-gray-900 dark:text-white', child: Builder( builder: (context) => TextFormField( keyboardType: TextInputType.emailAddress, style: baseStyle.copyWith( fontSize: fontSize, height: lineHeight / fontSize, color: isDark ? Colors.white : Colors.black, ), decoration: InputDecoration( labelText: 'Email', labelStyle: labelStyle, floatingLabelStyle: labelStyle, floatingLabelBehavior: FloatingLabelBehavior.auto, isDense: true, filled: true, fillColor: isDark ? tailwindColors['gray']![900] : Colors.white, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9), border: border, enabledBorder: border, focusedBorder: border, ), ), ), ), Visibility( visible: false, child: const Style( 'absolute inset-y-0 start-3 -translate-y-5 bg-white px-0.5 text-sm font-medium text-gray-700 transition-transform peer-placeholder-shown:translate-y-0 peer-focus:-translate-y-5 dark:bg-gray-900 dark:text-white', display: DisplayType.inline, child: Text('Email'), ), ), ], ), ); }}<label for="Email" class="relative"> <input type="email" id="Email" placeholder="" class="peer mt-0.5 w-full rounded border-gray-300 shadow-sm sm:text-sm" />
<span class="absolute inset-y-0 start-3 -translate-y-5 bg-white px-0.5 text-sm font-medium text-gray-700 transition-transform peer-placeholder-shown:translate-y-0 peer-focus:-translate-y-5" > Email </span></label>