Layouts#
Overview#
Layouts in Duxt provide a way to wrap pages with common UI elements like headers, footers, sidebars, and navigation. Instead of repeating this code in every page, you define it once in a layout.
Creating Layouts#
Extend DuxtLayout and implement the build method:
// lib/shared/layouts/default_layout.dart
import 'package:duxt/duxt.dart';
class DefaultLayout extends DuxtLayout {
@override
Component build(BuildContext context, Component child) {
return div(classes: 'min-h-screen flex flex-col', [
header(classes: 'h-16 border-b flex items-center px-6', [
a(href: '/', [text('My App')]),
nav(classes: 'ml-auto flex gap-4', [
a(href: '/about', [text('About')]),
a(href: '/contact', [text('Contact')]),
]),
]),
main_(classes: 'flex-1', [child]),
footer(classes: 'h-16 border-t flex items-center justify-center', [
text('My App'),
]),
]);
}
}
Generate a layout with the CLI:
duxt g layout dashboard
duxt g l dashboard
Using Layouts#
Apply a layout to a page using the layout getter:
// lib/pages/index.dart
import 'package:duxt/duxt.dart';
import '../shared/layouts/default_layout.dart';
class HomePage extends DuxtPage {
@override
Type get layout => DefaultLayout;
@override
Component build(BuildContext context) {
return div([
h1([text('Welcome Home')]),
p([text('This content is wrapped by DefaultLayout')]),
]);
}
}
Nested Layouts#
Layouts can extend other layouts for nested structures:
// lib/shared/layouts/dashboard_layout.dart
import 'package:duxt/duxt.dart';
import 'default_layout.dart';
class DashboardLayout extends DuxtLayout {
@override
Type? get parent => DefaultLayout;
@override
Component build(BuildContext context, Component child) {
return div(classes: 'flex', [
aside(classes: 'w-64 border-r p-4', [
nav([
a(href: '/dashboard', [text('Overview')]),
a(href: '/dashboard/analytics', [text('Analytics')]),
a(href: '/dashboard/settings', [text('Settings')]),
]),
]),
div(classes: 'flex-1 p-6', [child]),
]);
}
}
The rendering order is: DefaultLayout -> DashboardLayout -> Page
Common Layouts#
Marketing Layout#
class MarketingLayout extends DuxtLayout {
@override
Component build(BuildContext context, Component child) {
return div(classes: 'min-h-screen', [
header(classes: 'sticky top-0 z-50 bg-white/80 backdrop-blur', [
div(classes: 'max-w-7xl mx-auto px-4 h-16 flex items-center', [
Logo(),
NavigationMenu(),
div(classes: 'ml-auto flex gap-4', [
DButton(label: 'Sign In', variant: DButtonVariant.ghost),
DButton(label: 'Get Started'),
]),
]),
]),
main_([child]),
Footer(),
]);
}
}
Dashboard Layout#
class DashboardLayout extends DuxtLayout {
@override
Component build(BuildContext context, Component child) {
return div(classes: 'min-h-screen flex', [
aside(classes: 'w-64 bg-gray-900 text-white', [
div(classes: 'p-4', [Logo()]),
DashboardNav(),
]),
div(classes: 'flex-1 flex flex-col', [
header(classes: 'h-16 border-b flex items-center px-6', [
SearchBar(),
div(classes: 'ml-auto flex items-center gap-4', [
NotificationBell(),
UserMenu(),
]),
]),
main_(classes: 'flex-1 p-6 bg-gray-50', [child]),
]),
]);
}
}
Namespace Layout#
Namespace layouts auto-wrap all routes in a namespace. Create one with:
duxt g layout Admin
This creates lib/admin/layouts/default.dart:
import 'package:jaspr/jaspr.dart';
import 'package:jaspr/dom.dart';
class AdminLayout extends StatelessComponent {
final Component child;
const AdminLayout({super.key, required this.child});
@override
Component build(BuildContext context) {
return div(classes: 'min-h-screen', [
header(classes: 'bg-gray-900 border-b border-gray-700', [
div(classes: 'max-w-7xl mx-auto px-4 py-3 flex items-center justify-between', [
a(href: '/admin', classes: 'text-white font-semibold', [
text('Admin'),
]),
nav(classes: 'flex gap-4', [
a(href: '/admin/dashboard', classes: 'text-gray-300 hover:text-white', [text('Dashboard')]),
a(href: '/admin/posts', classes: 'text-gray-300 hover:text-white', [text('Posts')]),
a(href: '/admin/users', classes: 'text-gray-300 hover:text-white', [text('Users')]),
]),
]),
]),
main_(classes: 'max-w-7xl mx-auto px-4 py-8', [
child,
]),
]);
}
}
All /admin/* routes are automatically wrapped. No per-page configuration needed.
See Namespaces for details.
Auth Layout#
class AuthLayout extends DuxtLayout {
@override
Component build(BuildContext context, Component child) {
return div(classes: 'min-h-screen flex', [
div(classes: 'hidden lg:flex w-1/2 bg-gradient-to-br from-cyan-600 to-blue-600', [
div(classes: 'flex flex-col justify-center items-center text-white p-12', [
Logo(size: 'lg'),
h2(classes: 'text-3xl font-bold mt-8', [text('Welcome back')]),
p(classes: 'text-white/80 mt-2', [text('Sign in to continue')]),
]),
]),
div(classes: 'flex-1 flex items-center justify-center p-8', [
div(classes: 'w-full max-w-md', [child]),
]),
]);
}
}