Skip to content
HyperUI for Flutter requires the Style package — this site itself ships no pub package.

Vertical Menu

Ported from HyperUI’s Vertical Menu — see Attribution.

class VerticalMenu1Example extends StatelessWidget {
const VerticalMenu1Example({super.key});
@override
Widget build(BuildContext context) {
return Style(
'mx-auto max-w-sm p-6',
child: Style(
'space-y-1',
children: [
const Style(
'block rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700',
cursor: SystemMouseCursors.click,
child: Text('General'),
),
for (final label in const ['Teams', 'Billing', 'Invoices', 'Account'])
Style('block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700', cursor: SystemMouseCursors.click, child: Text(label)),
],
),
);
}
}

Includes a dark: variant — toggle the site theme (top right) to preview it.

class VerticalMenu2Example extends StatelessWidget {
const VerticalMenu2Example({super.key});
@override
Widget build(BuildContext context) {
return Style(
'mx-auto max-w-sm p-6',
child: Style(
'space-y-1',
children: [
const Style(
'group flex items-center justify-between rounded-lg bg-gray-100 px-4 py-2 text-gray-700 dark:bg-gray-800 dark:text-gray-200',
cursor: SystemMouseCursors.click,
child: Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('General'),
),
),
Style(
'group flex items-center justify-between rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
children: const [
Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Teams'),
),
Style(
'shrink-0 rounded-full bg-gray-100 px-3 py-0.5 text-xs text-gray-600 group-hover:bg-gray-200 group-hover:text-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:group-hover:bg-gray-700 dark:group-hover:text-gray-300',
display: DisplayType.inline,
child: Text('5'),
),
],
),
const Style(
'group flex items-center justify-between rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
child: Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Billing'),
),
),
Style(
'group flex items-center justify-between rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
children: const [
Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Invoices'),
),
Style(
'shrink-0 rounded-full bg-gray-100 px-3 py-0.5 text-xs text-gray-600 group-hover:bg-gray-200 group-hover:text-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:group-hover:bg-gray-700 dark:group-hover:text-gray-300',
display: DisplayType.inline,
child: Text('3'),
),
],
),
const Style(
'group flex items-center justify-between rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
child: Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Account'),
),
),
],
),
);
}
}

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 _generalIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>''';
const _teamsIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/></svg>''';
const _billingIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/></svg>''';
const _invoicesIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z"/></svg>''';
const _accountIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>''';
class VerticalMenu3Example extends StatelessWidget {
const VerticalMenu3Example({super.key});
@override
Widget build(BuildContext context) {
return Style(
'mx-auto max-w-sm p-6',
child: Style(
'space-y-1',
children: [
Style(
'flex items-center gap-2 rounded-lg bg-gray-100 px-4 py-2 text-gray-700 dark:bg-gray-800 dark:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_generalIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('General'),
),
],
),
Style(
'flex items-center gap-2 rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_teamsIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Teams'),
),
],
),
Style(
'flex items-center gap-2 rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_billingIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Billing'),
),
],
),
Style(
'flex items-center gap-2 rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_invoicesIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Invoices'),
),
],
),
Style(
'flex items-center gap-2 rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_accountIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Account'),
),
],
),
],
),
);
}
}

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 _generalIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>''';
const _teamsIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/></svg>''';
const _billingIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/></svg>''';
const _invoicesIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z"/></svg>''';
const _accountIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>''';
class VerticalMenu4Example extends StatelessWidget {
const VerticalMenu4Example({super.key});
@override
Widget build(BuildContext context) {
return Style(
'mx-auto max-w-sm p-6',
child: Style(
'space-y-1',
children: [
Style(
'flex items-center gap-2 rounded-lg bg-gray-100 px-4 py-2 text-gray-700 dark:bg-gray-800 dark:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_generalIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('General'),
),
],
),
Style(
'group flex items-center justify-between rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
Style(
'flex items-center gap-2',
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_teamsIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Teams'),
),
],
),
const Style(
'shrink-0 rounded-full bg-gray-100 px-3 py-0.5 text-xs text-gray-600 group-hover:bg-gray-200 group-hover:text-gray-700 dark:bg-gray-800 dark:text-gray-200 dark:group-hover:bg-gray-900 dark:group-hover:text-gray-100',
display: DisplayType.inline,
child: Text('5'),
),
],
),
Style(
'flex items-center gap-2 rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_billingIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Billing'),
),
],
),
Style(
'group flex items-center justify-between rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
Style(
'flex items-center gap-2',
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_invoicesIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Invoices'),
),
],
),
const Style(
'shrink-0 rounded-full bg-gray-100 px-3 py-0.5 text-xs text-gray-600 group-hover:bg-gray-200 group-hover:text-gray-700 dark:bg-gray-800 dark:text-gray-200 dark:group-hover:bg-gray-900 dark:group-hover:text-gray-100',
display: DisplayType.inline,
child: Text('3'),
),
],
),
Style(
'flex items-center gap-2 rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_accountIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Account'),
),
],
),
],
),
);
}
}

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 _generalIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>''';
const _teamsIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/></svg>''';
const _billingIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/></svg>''';
const _invoicesIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z"/></svg>''';
const _accountIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>''';
class VerticalMenu5Example extends StatelessWidget {
const VerticalMenu5Example({super.key});
@override
Widget build(BuildContext context) {
return Style(
'mx-auto max-w-sm p-6',
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Style(
'flex items-center gap-2 border-s-[3px] border-blue-500 bg-blue-50 px-4 py-3 text-blue-700 dark:bg-blue-500/20 dark:text-blue-50',
cursor: SystemMouseCursors.click,
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_generalIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('General'),
),
],
),
Style(
'flex items-center gap-2 border-s-[3px] border-transparent px-4 py-3 text-gray-500 hover:border-gray-100 hover:bg-gray-50 hover:text-gray-700 dark:text-gray-400 dark:hover:border-gray-700 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_teamsIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Teams'),
),
],
),
Style(
'flex items-center gap-2 border-s-[3px] border-transparent px-4 py-3 text-gray-500 hover:border-gray-100 hover:bg-gray-50 hover:text-gray-700 dark:text-gray-400 dark:hover:border-gray-700 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_billingIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Billing'),
),
],
),
Style(
'flex items-center gap-2 border-s-[3px] border-transparent px-4 py-3 text-gray-500 hover:border-gray-100 hover:bg-gray-50 hover:text-gray-700 dark:text-gray-400 dark:hover:border-gray-700 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_invoicesIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Invoices'),
),
],
),
Style(
'flex items-center gap-2 border-s-[3px] border-transparent px-4 py-3 text-gray-500 hover:border-gray-100 hover:bg-gray-50 hover:text-gray-700 dark:text-gray-400 dark:hover:border-gray-700 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_accountIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Account'),
),
],
),
],
),
);
}
}
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 _chevronDownSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/></svg>''';
class VerticalMenu6Example extends StatefulWidget {
const VerticalMenu6Example({super.key});
@override
State<VerticalMenu6Example> createState() => _VerticalMenu6ExampleState();
}
class _VerticalMenu6ExampleState extends State<VerticalMenu6Example> {
bool _teamsOpen = false;
bool _accountOpen = false;
@override
Widget build(BuildContext context) {
return Style(
'mx-auto max-w-sm p-6',
child: Style(
'space-y-1',
children: [
const Style(
'block rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700',
cursor: SystemMouseCursors.click,
child: Text('General'),
),
Style(
'group [&_summary::-webkit-details-marker]:hidden',
open: _teamsOpen,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => setState(() => _teamsOpen = !_teamsOpen),
child: Style(
'flex cursor-pointer items-center justify-between rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700',
cursor: SystemMouseCursors.click,
children: [
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Teams'),
),
Style(
'shrink-0 transition duration-300 group-open:-rotate-180',
child: const Style(
'size-5',
child: CurrentColorIcon(_chevronDownSvg),
),
),
],
),
),
if (_teamsOpen)
Style(
'mt-2 space-y-1 px-4',
children: const [
Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700',
cursor: SystemMouseCursors.click,
child: Text('Banned Users'),
),
Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700',
cursor: SystemMouseCursors.click,
child: Text('Calendar'),
),
],
),
],
),
const Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700',
cursor: SystemMouseCursors.click,
child: Text('Billing'),
),
const Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700',
cursor: SystemMouseCursors.click,
child: Text('Invoices'),
),
Style(
'group [&_summary::-webkit-details-marker]:hidden',
open: _accountOpen,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => setState(() => _accountOpen = !_accountOpen),
child: Style(
'flex cursor-pointer items-center justify-between rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700',
cursor: SystemMouseCursors.click,
children: [
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Account'),
),
Style(
'shrink-0 transition duration-300 group-open:-rotate-180',
child: const Style(
'size-5',
child: CurrentColorIcon(_chevronDownSvg),
),
),
],
),
),
if (_accountOpen)
Style(
'mt-2 space-y-1 px-4',
children: [
const Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700',
cursor: SystemMouseCursors.click,
child: Text('Details'),
),
const Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700',
cursor: SystemMouseCursors.click,
child: Text('Security'),
),
const Style(
'w-full rounded-lg px-4 py-2 [text-align:_inherit] text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700',
child: Text('Logout'),
),
],
),
],
),
],
),
);
}
}

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 _generalIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>''';
const _teamsIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/></svg>''';
const _billingIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/></svg>''';
const _invoicesIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z"/></svg>''';
const _accountIconSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>''';
const _chevronDownSvg =
'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/></svg>''';
class VerticalMenu7Example extends StatefulWidget {
const VerticalMenu7Example({super.key});
@override
State<VerticalMenu7Example> createState() => _VerticalMenu7ExampleState();
}
class _VerticalMenu7ExampleState extends State<VerticalMenu7Example> {
bool _teamsOpen = false;
bool _accountOpen = false;
@override
Widget build(BuildContext context) {
return Style(
'mx-auto max-w-sm p-6',
child: Style(
'space-y-1',
children: [
Style(
'flex items-center gap-2 rounded-lg bg-gray-100 px-4 py-2 text-gray-700 dark:bg-gray-800 dark:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_generalIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('General'),
),
],
),
Style(
'group [&_summary::-webkit-details-marker]:hidden',
open: _teamsOpen,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => setState(() => _teamsOpen = !_teamsOpen),
child: Style(
'group flex items-center justify-between rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
open: _teamsOpen,
cursor: SystemMouseCursors.click,
children: [
Style(
'flex items-center gap-2',
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_teamsIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Teams'),
),
],
),
Style(
'shrink-0 transition duration-300 group-open:-rotate-180',
child: const Style(
'size-5',
child: CurrentColorIcon(_chevronDownSvg),
),
),
],
),
),
if (_teamsOpen)
Style(
'mt-2 space-y-1 px-4',
children: const [
Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
child: Text('Banned Users'),
),
Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
child: Text('Calendar'),
),
],
),
],
),
Style(
'flex items-center gap-2 rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_billingIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Billing'),
),
],
),
Style(
'flex items-center gap-2 rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_invoicesIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Invoices'),
),
],
),
Style(
'group [&_summary::-webkit-details-marker]:hidden',
open: _accountOpen,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => setState(() => _accountOpen = !_accountOpen),
child: Style(
'group flex items-center justify-between rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
open: _accountOpen,
cursor: SystemMouseCursors.click,
children: [
Style(
'flex items-center gap-2',
children: [
const Style(
'size-5 opacity-75',
child: CurrentColorIcon(_accountIconSvg),
),
const Style(
'text-sm font-medium',
display: DisplayType.inline,
child: Text('Account'),
),
],
),
Style(
'shrink-0 transition duration-300 group-open:-rotate-180',
child: const Style(
'size-5',
child: CurrentColorIcon(_chevronDownSvg),
),
),
],
),
),
if (_accountOpen)
Style(
'mt-2 space-y-1 px-4',
children: const [
Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
child: Text('Details'),
),
Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
child: Text('Security'),
),
Style(
'w-full rounded-lg px-4 py-2 [text-align:_inherit] text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
child: Text('Logout'),
),
],
),
],
),
],
),
);
}
}

Includes a dark: variant — toggle the site theme (top right) to preview it.

class VerticalMenu8Example extends StatelessWidget {
const VerticalMenu8Example({super.key});
@override
Widget build(BuildContext context) {
return Style(
'mx-auto max-w-sm p-6',
child: Style(
'flow-root',
child: Style(
'-my-2 divide-y divide-gray-100 dark:divide-gray-800',
children: [
Style(
'py-2',
child: Style(
'space-y-1',
children: const [
Style(
'block rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 dark:bg-gray-800 dark:text-gray-200',
cursor: SystemMouseCursors.click,
child: Text('Profile'),
),
Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
child: Text('Team'),
),
Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
child: Text('Projects'),
),
Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
child: Text('Meetings'),
),
Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
child: Text('Calendar'),
),
],
),
),
Style(
'py-2',
child: Style(
'space-y-1',
children: const [
Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
child: Text('Update'),
),
Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
child: Text('Help'),
),
Style(
'block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
cursor: SystemMouseCursors.click,
child: Text('Settings'),
),
],
),
),
Style(
'py-2',
child: const Style(
'block w-full rounded-lg px-4 py-2 [text-align:_inherit] text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200',
child: Text('Logout'),
),
),
],
),
),
);
}
}