Modals
Ported from HyperUI’s Modals — see Attribution. Each preview below shows the modal’s closed
state (just the “Open” trigger button) — hyperui’s own <dialog> has no
declarative always-open state; toggling it live is out of scope for this
static gallery (same “default DOM state only” convention as every checkbox/
toggle/radio-input in this corpus).
Includes a dark: variant — toggle the site theme (top right) to preview it.
const _description = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque ' 'euismod, nisi eu consectetur. Sed do eiusmod tempor incididunt ut ' 'labore et dolore magna aliqua.';
class Modals1Example extends StatelessWidget { const Modals1Example({super.key});
@override Widget build(BuildContext context) { return Stack( children: [ Style( 'p-6', child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => _openModal(context), child: Style( 'rounded border border-gray-300 bg-gray-100 px-3 py-1.5 text-sm text-gray-900 dark:border-gray-600 dark:bg-gray-800 dark:text-white', cursor: SystemMouseCursors.click, child: const Text('Open'), ), ), ), ], ); }
void _openModal(BuildContext context) { showDialog<void>( context: context, barrierColor: Colors.transparent, useSafeArea: false, useRootNavigator: false, builder: (dialogContext) => Stack( children: [ const Positioned.fill(child: IgnorePointer(child: _ModalBackdrop())), Center( child: Material( type: MaterialType.transparency, child: Style( 'm-auto max-w-xl rounded-lg bg-white p-6 shadow-lg backdrop:bg-black/50 dark:bg-gray-900 dark:backdrop:bg-white/50', child: Style( 'flex flex-col gap-4', children: [ Style( 'text-xl font-bold text-gray-900 sm:text-2xl dark:text-white', child: const Text('Modal Title'), ), Style( 'text-pretty text-gray-700 dark:text-gray-200', child: const Text(_description), ), ], ), ), ), ), ], ), ); }}
class _ModalBackdrop extends StatelessWidget { const _ModalBackdrop();
@override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return ColoredBox(color: Color(isDark ? 0x80FFFFFF : 0x80000000)); }}<button data-modal-open class="rounded border border-gray-300 bg-gray-100 px-3 py-1.5 text-sm text-gray-900"> Open</button>
<dialog closedby="any" aria-labelledby="modalTitle" aria-describedby="modalDescription" class="m-auto max-w-xl rounded-lg bg-white p-6 shadow-lg backdrop:bg-black/50"> <div class="flex flex-col gap-4"> <h2 id="modalTitle" class="text-xl font-bold text-gray-900 sm:text-2xl">Modal Title</h2>
<p id="modalDescription" class="text-pretty text-gray-700"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque euismod, nisi eu consectetur. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </p> </div></dialog>
<script> const modalEl = document.querySelector('dialog') const openButtonEl = document.querySelector('[data-modal-open]')
openButtonEl.addEventListener('click', () => modalEl.showModal())
document.querySelectorAll('[data-modal-close]').forEach((buttonEl) => { buttonEl.addEventListener('click', () => modalEl.close()) })</script>Base with close
Section titled “Base with close”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 _closeSvg = '''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>''';
const _description = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque ' 'euismod, nisi eu consectetur. Sed do eiusmod tempor incididunt ut ' 'labore et dolore magna aliqua.';
class Modals2Example extends StatelessWidget { const Modals2Example({super.key});
@override Widget build(BuildContext context) { return Stack( children: [ Style( 'p-6', child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => _openModal(context), child: Style( 'rounded border border-gray-300 bg-gray-100 px-3 py-1.5 text-sm text-gray-900 dark:border-gray-600 dark:bg-gray-800 dark:text-white', cursor: SystemMouseCursors.click, child: const Text('Open'), ), ), ), ], ); }
void _openModal(BuildContext context) { showDialog<void>( context: context, barrierColor: Colors.transparent, useSafeArea: false, useRootNavigator: false, builder: (dialogContext) => Stack( children: [ const Positioned.fill(child: IgnorePointer(child: _ModalBackdrop())), Center( child: Material( type: MaterialType.transparency, child: Style( 'm-auto max-w-xl rounded-lg bg-white p-6 shadow-lg backdrop:bg-black/50 dark:bg-gray-900 dark:backdrop:bg-white/50', child: Style( 'flex flex-col gap-4', children: [ Style( 'flex items-start justify-between', children: [ Style( 'text-xl font-bold text-gray-900 sm:text-2xl dark:text-white', child: const Text('Modal Title'), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => Navigator.pop(dialogContext), child: Style( '-me-4 -mt-4 rounded-full p-2 text-gray-600 transition-colors hover:bg-gray-50 hover:text-gray-900 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none dark:text-gray-500 dark:hover:bg-gray-800 dark:hover:text-gray-300 dark:focus:ring-indigo-300 dark:focus:ring-offset-gray-900 dark:focus-visible:ring-indigo-300 dark:focus-visible:ring-offset-gray-900', cursor: SystemMouseCursors.click, child: const Focus( autofocus: true, child: Style( 'size-5', child: CurrentColorIcon(_closeSvg), ), ), ), ), ], ), Style( 'text-pretty text-gray-700 dark:text-gray-200', child: const Text(_description), ), ], ), ), ), ), ], ), ); }}
class _ModalBackdrop extends StatelessWidget { const _ModalBackdrop();
@override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return ColoredBox(color: Color(isDark ? 0x80FFFFFF : 0x80000000)); }}<button data-modal-open class="rounded border border-gray-300 bg-gray-100 px-3 py-1.5 text-sm text-gray-900"> Open</button>
<dialog closedby="any" aria-labelledby="modalTitle" aria-describedby="modalDescription" class="m-auto max-w-xl rounded-lg bg-white p-6 shadow-lg backdrop:bg-black/50"> <div class="flex flex-col gap-4"> <div class="flex items-start justify-between"> <h2 id="modalTitle" class="text-xl font-bold text-gray-900 sm:text-2xl">Modal Title</h2>
<button type="button" data-modal-close class="-me-4 -mt-4 rounded-full p-2 text-gray-600 transition-colors hover:bg-gray-50 hover:text-gray-900 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none" aria-label="Close" > <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="size-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" > <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </svg> </button> </div>
<p id="modalDescription" class="text-pretty text-gray-700"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque euismod, nisi eu consectetur. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </p> </div></dialog>
<script> const modalEl = document.querySelector('dialog') const openButtonEl = document.querySelector('[data-modal-open]')
openButtonEl.addEventListener('click', () => modalEl.showModal())
document.querySelectorAll('[data-modal-close]').forEach((buttonEl) => { buttonEl.addEventListener('click', () => modalEl.close()) })</script>Actions
Section titled “Actions”Includes a dark: variant — toggle the site theme (top right) to preview it.
const _description = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque ' 'euismod, nisi eu consectetur. Sed do eiusmod tempor incididunt ut ' 'labore et dolore magna aliqua.';
class Modals3Example extends StatelessWidget { const Modals3Example({super.key});
@override Widget build(BuildContext context) { return Stack( children: [ Style( 'p-6', child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => _openModal(context), child: Style( 'rounded border border-gray-300 bg-gray-100 px-3 py-1.5 text-sm text-gray-900 dark:border-gray-600 dark:bg-gray-800 dark:text-white', cursor: SystemMouseCursors.click, child: const Text('Open'), ), ), ), ], ); }
void _openModal(BuildContext context) { showDialog<void>( context: context, barrierColor: Colors.transparent, useSafeArea: false, useRootNavigator: false, builder: (dialogContext) => Stack( children: [ const Positioned.fill(child: IgnorePointer(child: _ModalBackdrop())), Center( child: Material( type: MaterialType.transparency, child: Style( 'm-auto max-w-xl rounded-lg bg-white p-6 shadow-lg backdrop:bg-black/50 dark:bg-gray-900 dark:backdrop:bg-white/50', child: Style( 'flex flex-col gap-4', children: [ Style( 'text-xl font-bold text-gray-900 sm:text-2xl dark:text-white', child: const Text('Modal Title'), ), Style( 'text-pretty text-gray-700 dark:text-gray-200', child: const Text(_description), ), Style( 'flex justify-end gap-2', children: [ GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => Navigator.pop(dialogContext), child: Style( 'rounded bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-200 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700 dark:focus:ring-indigo-300 dark:focus:ring-offset-gray-900 dark:focus-visible:ring-indigo-300 dark:focus-visible:ring-offset-gray-900', cursor: SystemMouseCursors.click, child: const Focus( autofocus: true, child: Text('Cancel'), ), ), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => Navigator.pop(dialogContext), child: Style( 'rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none dark:bg-blue-300 dark:text-gray-900 dark:hover:bg-blue-200 dark:focus:ring-indigo-300 dark:focus:ring-offset-gray-900 dark:focus-visible:ring-indigo-300 dark:focus-visible:ring-offset-gray-900', cursor: SystemMouseCursors.click, child: const Text('Done'), ), ), ], ), ], ), ), ), ), ], ), ); }}
class _ModalBackdrop extends StatelessWidget { const _ModalBackdrop();
@override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return ColoredBox(color: Color(isDark ? 0x80FFFFFF : 0x80000000)); }}<button data-modal-open class="rounded border border-gray-300 bg-gray-100 px-3 py-1.5 text-sm text-gray-900"> Open</button>
<dialog closedby="any" aria-labelledby="modalTitle" aria-describedby="modalDescription" class="m-auto max-w-xl rounded-lg bg-white p-6 shadow-lg backdrop:bg-black/50"> <div class="flex flex-col gap-4"> <h2 id="modalTitle" class="text-xl font-bold text-gray-900 sm:text-2xl">Modal Title</h2>
<p id="modalDescription" class="text-pretty text-gray-700"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque euismod, nisi eu consectetur. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </p>
<div class="flex justify-end gap-2"> <button type="button" data-modal-close class="rounded bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-200 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none" > Cancel </button>
<button type="button" data-modal-close class="rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none" > Done </button> </div> </div></dialog>
<script> const modalEl = document.querySelector('dialog') const openButtonEl = document.querySelector('[data-modal-open]')
openButtonEl.addEventListener('click', () => modalEl.showModal())
document.querySelectorAll('[data-modal-close]').forEach((buttonEl) => { buttonEl.addEventListener('click', () => modalEl.close()) })</script>Actions with close
Section titled “Actions with close”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 _closeSvg = '''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>''';
const _description = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque ' 'euismod, nisi eu consectetur. Sed do eiusmod tempor incididunt ut ' 'labore et dolore magna aliqua.';
class Modals4Example extends StatelessWidget { const Modals4Example({super.key});
@override Widget build(BuildContext context) { return Stack( children: [ Style( 'p-6', child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => _openModal(context), child: Style( 'rounded border border-gray-300 bg-gray-100 px-3 py-1.5 text-sm text-gray-900 dark:border-gray-600 dark:bg-gray-800 dark:text-white', cursor: SystemMouseCursors.click, child: const Text('Open'), ), ), ), ], ); }
void _openModal(BuildContext context) { showDialog<void>( context: context, barrierColor: Colors.transparent, useSafeArea: false, useRootNavigator: false, builder: (dialogContext) => Stack( children: [ const Positioned.fill(child: IgnorePointer(child: _ModalBackdrop())), Center( child: Material( type: MaterialType.transparency, child: Style( 'm-auto max-w-xl rounded-lg bg-white p-6 shadow-lg backdrop:bg-black/50 dark:bg-gray-900 dark:backdrop:bg-white/50', child: Style( 'flex flex-col gap-4', children: [ Style( 'flex items-start justify-between', children: [ Style( 'text-xl font-bold text-gray-900 sm:text-2xl dark:text-white', child: const Text('Modal Title'), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => Navigator.pop(dialogContext), child: Style( '-me-4 -mt-4 rounded-full p-2 text-gray-600 transition-colors hover:bg-gray-50 hover:text-gray-900 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none dark:text-gray-500 dark:hover:bg-gray-800 dark:hover:text-gray-300 dark:focus:ring-indigo-300 dark:focus:ring-offset-gray-900 dark:focus-visible:ring-indigo-300 dark:focus-visible:ring-offset-gray-900', cursor: SystemMouseCursors.click, child: const Focus( autofocus: true, child: Style( 'size-5', child: CurrentColorIcon(_closeSvg), ), ), ), ), ], ), Style( 'text-pretty text-gray-700 dark:text-gray-200', child: const Text(_description), ), Style( 'flex justify-end gap-2', children: [ GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => Navigator.pop(dialogContext), child: Style( 'rounded bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-200 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700 dark:focus:ring-indigo-300 dark:focus:ring-offset-gray-900 dark:focus-visible:ring-indigo-300 dark:focus-visible:ring-offset-gray-900', cursor: SystemMouseCursors.click, child: const Text('Cancel'), ), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => Navigator.pop(dialogContext), child: Style( 'rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none dark:bg-blue-300 dark:text-gray-900 dark:hover:bg-blue-200 dark:focus:ring-indigo-300 dark:focus:ring-offset-gray-900 dark:focus-visible:ring-indigo-300 dark:focus-visible:ring-offset-gray-900', cursor: SystemMouseCursors.click, child: const Text('Done'), ), ), ], ), ], ), ), ), ), ], ), ); }}
class _ModalBackdrop extends StatelessWidget { const _ModalBackdrop();
@override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return ColoredBox(color: Color(isDark ? 0x80FFFFFF : 0x80000000)); }}<button data-modal-open class="rounded border border-gray-300 bg-gray-100 px-3 py-1.5 text-sm text-gray-900"> Open</button>
<dialog closedby="any" aria-labelledby="modalTitle" aria-describedby="modalDescription" class="m-auto max-w-xl rounded-lg bg-white p-6 shadow-lg backdrop:bg-black/50"> <div class="flex flex-col gap-4"> <div class="flex items-start justify-between"> <h2 id="modalTitle" class="text-xl font-bold text-gray-900 sm:text-2xl">Modal Title</h2>
<button type="button" data-modal-close class="-me-4 -mt-4 rounded-full p-2 text-gray-600 transition-colors hover:bg-gray-50 hover:text-gray-900 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none" aria-label="Close" > <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="size-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" > <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </svg> </button> </div>
<p id="modalDescription" class="text-pretty text-gray-700"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque euismod, nisi eu consectetur. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </p>
<div class="flex justify-end gap-2"> <button type="button" data-modal-close class="rounded bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-200 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none" > Cancel </button>
<button type="button" data-modal-close class="rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none" > Done </button> </div> </div></dialog>
<script> const modalEl = document.querySelector('dialog') const openButtonEl = document.querySelector('[data-modal-open]')
openButtonEl.addEventListener('click', () => modalEl.showModal())
document.querySelectorAll('[data-modal-close]').forEach((buttonEl) => { buttonEl.addEventListener('click', () => modalEl.close()) })</script>Includes a dark: variant — toggle the site theme (top right) to preview it.
const _description = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque ' 'euismod, nisi eu consectetur. Sed do eiusmod tempor incididunt ut ' 'labore et dolore magna aliqua.';
class Modals5Example extends StatelessWidget { const Modals5Example({super.key});
@override Widget build(BuildContext context) { return Stack( children: [ Style( 'p-6', child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => _openModal(context), child: Style( 'rounded border border-gray-300 bg-gray-100 px-3 py-1.5 text-sm text-gray-900 dark:border-gray-600 dark:bg-gray-800 dark:text-white', cursor: SystemMouseCursors.click, child: const Text('Open'), ), ), ), ], ); }
void _openModal(BuildContext context) { showDialog<void>( context: context, barrierColor: Colors.transparent, useSafeArea: false, useRootNavigator: false, builder: (dialogContext) { final isDark = Theme.of(dialogContext).brightness == Brightness.dark; final isSmUp = MediaQuery.sizeOf(dialogContext).width >= 640; final fontSize = isSmUp ? 14.0 : 16.0; final lineHeight = isSmUp ? 20.0 : 24.0; final baseStyle = Theme.of(dialogContext).textTheme.bodyLarge ?? const TextStyle();
return Stack( children: [ const Positioned.fill( child: IgnorePointer(child: _ModalBackdrop()), ), Center( child: Material( type: MaterialType.transparency, child: Style( 'm-auto max-w-xl rounded-lg bg-white p-6 shadow-lg backdrop:bg-black/50 dark:bg-gray-900 dark:backdrop:bg-white/50', child: Style( 'flex flex-col gap-4', children: [ Style( 'text-xl font-bold text-gray-900 sm:text-2xl dark:text-white', child: const Text('Modal Title'), ), Style( 'text-pretty text-gray-700 dark:text-gray-200', child: const Text(_description), ), Style( 'block', children: [ Style( 'text-sm font-medium text-gray-700 dark:text-gray-200', display: DisplayType.inline, child: const Text( 'Please type "Confirm" to complete action', ), ), Style( 'mt-0.5 w-full rounded border-gray-300 bg-white text-gray-900 shadow-sm focus:ring-2 focus:ring-indigo-600 sm:text-sm dark:border-gray-600 dark:bg-gray-900 dark:text-white dark:focus:ring-indigo-300', child: TextFormField( autofocus: true, style: baseStyle.copyWith( fontSize: fontSize, height: lineHeight / fontSize, color: isDark ? Colors.white : Colors.black, ), ), ), ], ), Style( 'flex justify-end gap-2', children: [ GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => Navigator.pop(dialogContext), child: Style( 'rounded bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-200 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700 dark:focus:ring-indigo-300 dark:focus:ring-offset-gray-900 dark:focus-visible:ring-indigo-300 dark:focus-visible:ring-offset-gray-900', cursor: SystemMouseCursors.click, child: const Text('Cancel'), ), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => Navigator.pop(dialogContext), child: Style( 'rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none dark:bg-blue-300 dark:text-gray-900 dark:hover:bg-blue-200 dark:focus:ring-indigo-300 dark:focus:ring-offset-gray-900 dark:focus-visible:ring-indigo-300 dark:focus-visible:ring-offset-gray-900', cursor: SystemMouseCursors.click, child: const Text('Done'), ), ), ], ), ], ), ), ), ), ], ); }, ); }}
class _ModalBackdrop extends StatelessWidget { const _ModalBackdrop();
@override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return ColoredBox(color: Color(isDark ? 0x80FFFFFF : 0x80000000)); }}<button data-modal-open class="rounded border border-gray-300 bg-gray-100 px-3 py-1.5 text-sm text-gray-900"> Open</button>
<dialog closedby="any" aria-labelledby="modalTitle" aria-describedby="modalDescription" class="m-auto max-w-xl rounded-lg bg-white p-6 shadow-lg backdrop:bg-black/50"> <div class="flex flex-col gap-4"> <h2 id="modalTitle" class="text-xl font-bold text-gray-900 sm:text-2xl">Modal Title</h2>
<p id="modalDescription" class="text-pretty text-gray-700"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque euismod, nisi eu consectetur. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </p>
<label for="Confirm" class="block"> <span class="text-sm font-medium text-gray-700"> Please type "Confirm" to complete action </span>
<input type="text" id="Confirm" class="mt-0.5 w-full rounded border-gray-300 bg-white text-gray-900 shadow-sm focus:ring-2 focus:ring-indigo-600 sm:text-sm" /> </label>
<div class="flex justify-end gap-2"> <button type="button" data-modal-close class="rounded bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-200 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none" > Cancel </button>
<button type="button" data-modal-close class="rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none" > Done </button> </div> </div></dialog>
<script> const modalEl = document.querySelector('dialog') const openButtonEl = document.querySelector('[data-modal-open]')
openButtonEl.addEventListener('click', () => modalEl.showModal())
document.querySelectorAll('[data-modal-close]').forEach((buttonEl) => { buttonEl.addEventListener('click', () => modalEl.close()) })</script>Input with close
Section titled “Input with close”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 _closeSvg = '''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>''';
const _description = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque ' 'euismod, nisi eu consectetur. Sed do eiusmod tempor incididunt ut ' 'labore et dolore magna aliqua.';
class Modals6Example extends StatelessWidget { const Modals6Example({super.key});
@override Widget build(BuildContext context) { return Stack( children: [ Style( 'p-6', child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => _openModal(context), child: Style( 'rounded border border-gray-300 bg-gray-100 px-3 py-1.5 text-sm text-gray-900 dark:border-gray-600 dark:bg-gray-800 dark:text-white', cursor: SystemMouseCursors.click, child: const Text('Open'), ), ), ), ], ); }
void _openModal(BuildContext context) { showDialog<void>( context: context, barrierColor: Colors.transparent, useSafeArea: false, useRootNavigator: false, builder: (dialogContext) { final isDark = Theme.of(dialogContext).brightness == Brightness.dark; final isSmUp = MediaQuery.sizeOf(dialogContext).width >= 640; final fontSize = isSmUp ? 14.0 : 16.0; final lineHeight = isSmUp ? 20.0 : 24.0; final baseStyle = Theme.of(dialogContext).textTheme.bodyLarge ?? const TextStyle();
return Stack( children: [ const Positioned.fill( child: IgnorePointer(child: _ModalBackdrop()), ), Center( child: Material( type: MaterialType.transparency, child: Style( 'm-auto max-w-xl rounded-lg bg-white p-6 shadow-lg backdrop:bg-black/50 dark:bg-gray-900 dark:backdrop:bg-white/50', child: Style( 'flex flex-col gap-4', children: [ Style( 'flex items-start justify-between', children: [ Style( 'text-xl font-bold text-gray-900 sm:text-2xl dark:text-white', child: const Text('Modal Title'), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => Navigator.pop(dialogContext), child: Style( '-me-4 -mt-4 rounded-full p-2 text-gray-600 transition-colors hover:bg-gray-50 hover:text-gray-900 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none dark:text-gray-500 dark:hover:bg-gray-800 dark:hover:text-gray-300 dark:focus:ring-indigo-300 dark:focus:ring-offset-gray-900 dark:focus-visible:ring-indigo-300 dark:focus-visible:ring-offset-gray-900', cursor: SystemMouseCursors.click, child: const Focus( autofocus: true, child: Style( 'size-5', child: CurrentColorIcon(_closeSvg), ), ), ), ), ], ), Style( 'text-pretty text-gray-700 dark:text-gray-200', child: const Text(_description), ), Style( 'block', children: [ Style( 'text-sm font-medium text-gray-700 dark:text-gray-200', display: DisplayType.inline, child: const Text( 'Please type "Confirm" to complete action', ), ), Style( 'mt-0.5 w-full rounded border-gray-300 bg-white text-gray-900 shadow-sm sm:text-sm dark:border-gray-600 dark:bg-gray-900 dark:text-white', child: TextFormField( style: baseStyle.copyWith( fontSize: fontSize, height: lineHeight / fontSize, color: isDark ? Colors.white : Colors.black, ), ), ), ], ), Style( 'flex justify-end gap-2', children: [ GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => Navigator.pop(dialogContext), child: Style( 'rounded bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-200 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700 dark:focus:ring-indigo-300 dark:focus:ring-offset-gray-900 dark:focus-visible:ring-indigo-300 dark:focus-visible:ring-offset-gray-900', cursor: SystemMouseCursors.click, child: const Text('Cancel'), ), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => Navigator.pop(dialogContext), child: Style( 'rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none dark:bg-blue-300 dark:text-gray-900 dark:hover:bg-blue-200 dark:focus:ring-indigo-300 dark:focus:ring-offset-gray-900 dark:focus-visible:ring-indigo-300 dark:focus-visible:ring-offset-gray-900', cursor: SystemMouseCursors.click, child: const Text('Done'), ), ), ], ), ], ), ), ), ), ], ); }, ); }}
class _ModalBackdrop extends StatelessWidget { const _ModalBackdrop();
@override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return ColoredBox(color: Color(isDark ? 0x80FFFFFF : 0x80000000)); }}<button data-modal-open class="rounded border border-gray-300 bg-gray-100 px-3 py-1.5 text-sm text-gray-900"> Open</button>
<dialog closedby="any" aria-labelledby="modalTitle" aria-describedby="modalDescription" class="m-auto max-w-xl rounded-lg bg-white p-6 shadow-lg backdrop:bg-black/50"> <div class="flex flex-col gap-4"> <div class="flex items-start justify-between"> <h2 id="modalTitle" class="text-xl font-bold text-gray-900 sm:text-2xl">Modal Title</h2>
<button type="button" data-modal-close class="-me-4 -mt-4 rounded-full p-2 text-gray-600 transition-colors hover:bg-gray-50 hover:text-gray-900 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none" aria-label="Close" > <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="size-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" > <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </svg> </button> </div>
<p id="modalDescription" class="text-pretty text-gray-700"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque euismod, nisi eu consectetur. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </p>
<label for="Confirm" class="block"> <span class="text-sm font-medium text-gray-700"> Please type "Confirm" to complete action </span>
<input type="text" id="Confirm" class="mt-0.5 w-full rounded border-gray-300 bg-white text-gray-900 shadow-sm sm:text-sm" /> </label>
<div class="flex justify-end gap-2"> <button type="button" data-modal-close class="rounded bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-200 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none" > Cancel </button>
<button type="button" data-modal-close class="rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 focus:ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white focus-visible:outline-none" > Done </button> </div> </div></dialog>
<script> const modalEl = document.querySelector('dialog') const openButtonEl = document.querySelector('[data-modal-open]')
openButtonEl.addEventListener('click', () => modalEl.showModal())
document.querySelectorAll('[data-modal-close]').forEach((buttonEl) => { buttonEl.addEventListener('click', () => modalEl.close()) })</script>