Loaders
Ported from HyperUI’s Loaders — see Attribution.
Spinner
Section titled “Spinner”Includes a dark: variant — toggle the site theme (top right) to preview it.
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 _spinnerCircleSvg = '''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle></svg>''';
const _spinnerPathSvg = '''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>''';
class Loaders1Example extends StatelessWidget { const Loaders1Example({super.key});
@override Widget build(BuildContext context) { return Style( 'flex min-h-screen items-center justify-center p-6', child: Style( 'mx-auto size-8 animate-spin text-indigo-600 dark:text-indigo-300', child: Stack( children: const [ Positioned.fill( child: Style( 'opacity-25', child: CurrentColorIcon(_spinnerCircleSvg), ), ), Positioned.fill( child: Style( 'opacity-75', child: CurrentColorIcon(_spinnerPathSvg), ), ), ], ), ), ); }}<div role="status" aria-label="Loading"> <svg class="mx-auto size-8 animate-spin text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true" > <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" ></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" ></path> </svg></div>Spinner with text
Section titled “Spinner with text”Includes a dark: variant — toggle the site theme (top right) to preview it.
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 _spinnerCircleSvg = '''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle></svg>''';
const _spinnerPathSvg = '''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>''';
class Loaders2Example extends StatelessWidget { const Loaders2Example({super.key});
@override Widget build(BuildContext context) { return Style( 'flex min-h-screen items-center justify-center p-6', child: Style( 'text-center', child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ Style( 'mx-auto size-8 animate-spin text-indigo-600 dark:text-indigo-300', child: Stack( children: const [ Positioned.fill( child: Style( 'opacity-25', child: CurrentColorIcon(_spinnerCircleSvg), ), ), Positioned.fill( child: Style( 'opacity-75', child: CurrentColorIcon(_spinnerPathSvg), ), ), ], ), ), const Style( 'mt-4 font-medium text-gray-700 dark:text-gray-200', child: Text('Loading...'), ), ], ), ), ); }}<div class="text-center" role="status"> <svg class="mx-auto size-8 animate-spin text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true" > <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" ></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" ></path> </svg>
<p class="mt-4 font-medium text-gray-700">Loading...</p></div>Spinner inline
Section titled “Spinner inline”Includes a dark: variant — toggle the site theme (top right) to preview it.
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 _spinnerCircleSvg = '''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle></svg>''';
const _spinnerPathSvg = '''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>''';
class Loaders3Example extends StatelessWidget { const Loaders3Example({super.key});
@override Widget build(BuildContext context) { return Style( 'flex min-h-screen items-center justify-center p-6', child: Style( 'inline-flex items-center gap-3', children: [ Style( 'size-6 animate-spin text-indigo-600 dark:text-indigo-300', child: Stack( children: const [ Positioned.fill( child: Style( 'opacity-25', child: CurrentColorIcon(_spinnerCircleSvg), ), ), Positioned.fill( child: Style( 'opacity-75', child: CurrentColorIcon(_spinnerPathSvg), ), ), ], ), ), const Style( 'font-medium text-gray-700 dark:text-gray-200', child: Text('Loading...'), ), ], ), ); }}<div class="inline-flex items-center gap-3" role="status"> <svg class="size-6 animate-spin text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true" > <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" ></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" ></path> </svg>
<p class="font-medium text-gray-700">Loading...</p></div>Progress bar
Section titled “Progress bar”Includes a dark: variant — toggle the site theme (top right) to preview it.
class Loaders4Example extends StatelessWidget { const Loaders4Example({super.key});
@override Widget build(BuildContext context) { return Style( 'flex min-h-screen items-center justify-center p-6', child: Style( 'w-full max-w-sm', children: [ Style( 'h-2 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700', child: const Style( 'h-full w-[80%] animate-pulse bg-indigo-600 dark:bg-indigo-300', ), ), const Style( 'mt-4 text-center font-medium text-gray-700 dark:text-gray-200', child: Text('Loading...'), ), ], ), ); }}<div class="w-full max-w-sm" role="status"> <div class="h-2 overflow-hidden rounded-full bg-gray-200"> <div class="h-full w-[80%] animate-pulse bg-indigo-600"></div> </div>
<p class="mt-4 text-center font-medium text-gray-700">Loading...</p></div>Dots pulse
Section titled “Dots pulse”Includes a dark: variant — toggle the site theme (top right) to preview it.
class Loaders5Example extends StatelessWidget { const Loaders5Example({super.key});
@override Widget build(BuildContext context) { return Style( 'flex min-h-screen items-center justify-center p-6', child: Style( 'flex gap-2', children: const [ Style('size-3 animate-pulse rounded-full bg-indigo-600 dark:bg-indigo-300'), Style( 'size-3 animate-pulse rounded-full bg-indigo-600 [animation-delay:0.2s] dark:bg-indigo-300', ), Style( 'size-3 animate-pulse rounded-full bg-indigo-600 [animation-delay:0.4s] dark:bg-indigo-300', ), ], ), ); }}<div class="flex gap-2" role="status" aria-label="Loading"> <span class="size-3 animate-pulse rounded-full bg-indigo-600" aria-hidden="true"></span> <span class="size-3 animate-pulse rounded-full bg-indigo-600 [animation-delay:0.2s]" aria-hidden="true" ></span> <span class="size-3 animate-pulse rounded-full bg-indigo-600 [animation-delay:0.4s]" aria-hidden="true" ></span></div>Dots ping
Section titled “Dots ping”Includes a dark: variant — toggle the site theme (top right) to preview it.
class Loaders6Example extends StatelessWidget { const Loaders6Example({super.key});
@override Widget build(BuildContext context) { return Style( 'flex min-h-screen items-center justify-center p-6', child: Style( 'flex gap-2', children: const [ Style('size-3 animate-ping rounded-full bg-indigo-600 dark:bg-indigo-300'), Style( 'size-3 animate-ping rounded-full bg-indigo-600 [animation-delay:0.2s] dark:bg-indigo-300', ), Style( 'size-3 animate-ping rounded-full bg-indigo-600 [animation-delay:0.4s] dark:bg-indigo-300', ), ], ), ); }}<div class="flex gap-2" role="status" aria-label="Loading"> <span class="size-3 animate-ping rounded-full bg-indigo-600" aria-hidden="true"></span> <span class="size-3 animate-ping rounded-full bg-indigo-600 [animation-delay:0.2s]" aria-hidden="true" ></span> <span class="size-3 animate-ping rounded-full bg-indigo-600 [animation-delay:0.4s]" aria-hidden="true" ></span></div>Dots bounce
Section titled “Dots bounce”Includes a dark: variant — toggle the site theme (top right) to preview it.
class Loaders7Example extends StatelessWidget { const Loaders7Example({super.key});
@override Widget build(BuildContext context) { return Style( 'flex min-h-screen items-center justify-center p-6', child: Style( 'flex gap-2', children: const [ Style( 'size-3 animate-bounce rounded-full bg-indigo-600 dark:bg-indigo-300', ), Style( 'size-3 animate-bounce rounded-full bg-indigo-600 [animation-delay:0.2s] dark:bg-indigo-300', ), Style( 'size-3 animate-bounce rounded-full bg-indigo-600 [animation-delay:0.4s] dark:bg-indigo-300', ), ], ), ); }}<div class="flex gap-2" role="status" aria-label="Loading"> <span class="size-3 animate-bounce rounded-full bg-indigo-600" aria-hidden="true"></span> <span class="size-3 animate-bounce rounded-full bg-indigo-600 [animation-delay:0.2s]" aria-hidden="true" ></span> <span class="size-3 animate-bounce rounded-full bg-indigo-600 [animation-delay:0.4s]" aria-hidden="true" ></span></div>