# Layout Component ## Introduction The Layout component provides a complete application structure with fixed header and footer, a scrollable main content area, and optional collapsible side drawers. It's designed to be the foundation of your FastHTML application's UI. **Key features:** - Fixed header and footer that stay visible while scrolling - Collapsible left and right drawers for navigation, tools, or auxiliary content - Resizable drawers with drag handles - Automatic state persistence per session - Single instance per session (singleton pattern) **Common use cases:** - Application with navigation sidebar - Dashboard with tools panel - Admin interface with settings drawer - Documentation site with table of contents ## Quick Start Here's a minimal example showing an application with a navigation sidebar: ```python from fasthtml.common import * from myfasthtml.controls.Layout import Layout from myfasthtml.controls.helpers import mk from myfasthtml.core.commands import Command # Create the layout instance layout = Layout(parent=root_instance, app_name="My App") # Add navigation items to the left drawer layout.left_drawer.add( mk.mk(Div("Home"), command=Command(...)) ) layout.left_drawer.add( mk.mk(Div("About"), command=Command(...)) ) layout.left_drawer.add( mk.mk(Div("Contact"), command=Command(...)) ) # Set the main content layout.set_main( Div( H1("Welcome"), P("This is the main content area") ) ) # Render the layout return layout ``` This creates a complete application layout with: - A header displaying the app name and drawer toggle button - A collapsible left drawer with interactive navigation items - A main content area that updates when navigation items are clicked - An empty footer **Note:** Navigation items use commands to update the main content area without page reload. See the Commands section below for details. ## Basic Usage ### Creating a Layout The Layout component is a `SingleInstance`, meaning there's only one instance per session. Create it by providing a parent instance and an application name: ```python layout = Layout(parent=root_instance, app_name="My Application") ``` ### Content Zones The Layout provides six content zones where you can add components: ``` ┌──────────────────────────────────────────────────────────┐ │ Header │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ header_left │ │ header_right │ │ │ └─────────────────┘ └─────────────────┘ │ ├─────────┬────────────────────────────────────┬───────────┤ │ │ │ │ │ left │ │ right │ │ drawer │ Main Content │ drawer │ │ │ │ │ │ │ │ │ ├─────────┴────────────────────────────────────┴───────────┤ │ Footer │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ footer_left │ │ footer_right │ │ │ └─────────────────┘ └─────────────────┘ │ └──────────────────────────────────────────────────────────┘ ``` **Zone details:** | Zone | Typical Use | |----------------|-----------------------------------------------| | `header_left` | App logo, menu button, breadcrumbs | | `header_right` | User profile, notifications, settings | | `left_drawer` | Navigation menu, tree view, filters | | `right_drawer` | Tools panel, properties inspector, debug info | | `footer_left` | Copyright, legal links, version | | `footer_right` | Status indicators, connection state | ### Adding Content to Zones Use the `.add()` method to add components to any zone: ```python # Header layout.header_left.add(Div("Logo")) layout.header_right.add(Div("User: Admin")) # Drawers layout.left_drawer.add(Div("Navigation")) layout.right_drawer.add(Div("Tools")) # Footer layout.footer_left.add(Div("© 2024 My App")) layout.footer_right.add(Div("v1.0.0")) ``` ### Setting Main Content The main content area displays your page content and can be updated dynamically: ```python # Set initial content layout.set_main( Div( H1("Dashboard"), P("Welcome to your dashboard") ) ) # Update later (typically via commands) layout.set_main( Div( H1("Settings"), P("Configure your preferences") ) ) ``` ### Controlling Drawers By default, both drawers are visible. The drawer state is managed automatically: - Users can toggle drawers using the icon buttons in the header - Users can resize drawers by dragging the handle - Drawer state persists within the session The initial drawer widths are: - Left drawer: 250px - Right drawer: 250px These can be adjusted by users and the state is preserved automatically. ## Content System ### Understanding Groups Each content zone (header_left, header_right, drawers, footer) supports **groups** to organize related items. Groups are separated visually by dividers and can have optional labels. ### Adding Content to Groups When adding content, you can optionally specify a group name: ```python # Add items to different groups in the left drawer layout.left_drawer.add(Div("Dashboard"), group="main") layout.left_drawer.add(Div("Analytics"), group="main") layout.left_drawer.add(Div("Settings"), group="preferences") layout.left_drawer.add(Div("Profile"), group="preferences") ``` This creates two groups: - **main**: Dashboard, Analytics - **preferences**: Settings, Profile A visual divider automatically appears between groups. ### Custom Group Labels You can provide a custom FastHTML element to display as the group header: ```python # Add a styled group header layout.left_drawer.add_group( "Navigation", group_ft=Div("MAIN MENU", cls="font-bold text-sm opacity-60 px-2 py-1") ) # Then add items to this group layout.left_drawer.add(Div("Home"), group="Navigation") layout.left_drawer.add(Div("About"), group="Navigation") ``` ### Ungrouped Content If you don't specify a group, content is added to the default (`None`) group: ```python # These items are in the default group layout.left_drawer.add(Div("Quick Action 1")) layout.left_drawer.add(Div("Quick Action 2")) ``` ### Preventing Duplicates The Content system automatically prevents adding duplicate items based on their `id` attribute: ```python item = Div("Unique Item", id="my-item") layout.left_drawer.add(item) layout.left_drawer.add(item) # Ignored - already added ``` ### Group Rendering Options Groups render differently depending on the zone: **In drawers** (vertical layout): - Groups stack vertically - Dividers are horizontal lines - Group labels appear above their content **In header/footer** (horizontal layout): - Groups arrange side-by-side - Dividers are vertical lines - Group labels are typically hidden ## Advanced Features ### Resizable Drawers Both drawers can be resized by users via drag handles: - **Drag handle location**: - Left drawer: Right edge - Right drawer: Left edge - **Width constraints**: 150px (minimum) to 600px (maximum) - **Persistence**: Resized width is automatically saved in the session state Users can drag the handle to adjust drawer width. The new width is preserved throughout their session. ### Programmatic Drawer Control You can control drawers programmatically using commands: ```python # Toggle drawer visibility toggle_left = layout.commands.toggle_drawer("left") toggle_right = layout.commands.toggle_drawer("right") # Update drawer width update_left_width = layout.commands.update_drawer_width("left", width=300) update_right_width = layout.commands.update_drawer_width("right", width=350) ``` These commands are typically used with buttons or other interactive elements: ```python # Add a button to toggle the right drawer button = mk.button("Toggle Tools", command=layout.commands.toggle_drawer("right")) layout.header_right.add(button) ``` ### State Persistence The Layout automatically persists its state within the user's session: | State Property | Description | Default | |----------------------|---------------------------------|---------| | `left_drawer_open` | Whether left drawer is visible | `True` | | `right_drawer_open` | Whether right drawer is visible | `True` | | `left_drawer_width` | Left drawer width in pixels | `250` | | `right_drawer_width` | Right drawer width in pixels | `250` | State changes (toggle, resize) are automatically saved and restored within the session. ### Dynamic Content Updates Content zones can be updated dynamically during the session: ```python # Initial setup layout.left_drawer.add(Div("Item 1")) # Later, add more items (e.g., in a command handler) def add_dynamic_content(): layout.left_drawer.add(Div("New Item"), group="dynamic") return layout.left_drawer # Return updated drawer for HTMX swap ``` **Note**: When updating content dynamically, you typically return the updated zone to trigger an HTMX swap. ### CSS Customization The Layout uses CSS classes that you can customize: | Class | Element | |----------------------------|----------------------------------| | `mf-layout` | Root layout container | | `mf-layout-header` | Header section | | `mf-layout-footer` | Footer section | | `mf-layout-main` | Main content area | | `mf-layout-drawer` | Drawer container | | `mf-layout-left-drawer` | Left drawer specifically | | `mf-layout-right-drawer` | Right drawer specifically | | `mf-layout-drawer-content` | Scrollable content within drawer | | `mf-resizer` | Resize handle | | `mf-layout-group` | Content group wrapper | You can override these classes in your custom CSS to change colors, spacing, or behavior. ### User Profile Integration The Layout automatically includes a UserProfile component in the header right area. This component handles user authentication display and logout functionality when auth is enabled. ## Examples ### Example 1: Dashboard with Navigation Sidebar A typical dashboard application with a navigation menu in the left drawer: ```python from fasthtml.common import * from myfasthtml.controls.Layout import Layout from myfasthtml.controls.helpers import mk from myfasthtml.core.commands import Command # Create layout layout = Layout(parent=root_instance, app_name="Analytics Dashboard") # Navigation menu in left drawer def show_dashboard(): layout.set_main(Div(H1("Dashboard"), P("Overview of your metrics"))) return layout._mk_main() def show_reports(): layout.set_main(Div(H1("Reports"), P("Detailed analytics reports"))) return layout._mk_main() def show_settings(): layout.set_main(Div(H1("Settings"), P("Configure your preferences"))) return layout._mk_main() # Add navigation items with groups layout.left_drawer.add_group("main", group_ft=Div("MENU", cls="font-bold text-xs px-2 opacity-60")) layout.left_drawer.add(mk.mk(Div("Dashboard"), command=Command("nav_dash", "Show dashboard", show_dashboard)), group="main") layout.left_drawer.add(mk.mk(Div("Reports"), command=Command("nav_reports", "Show reports", show_reports)), group="main") layout.left_drawer.add_group("config", group_ft=Div("CONFIGURATION", cls="font-bold text-xs px-2 opacity-60")) layout.left_drawer.add(mk.mk(Div("Settings"), command=Command("nav_settings", "Show settings", show_settings)), group="config") # Header content layout.header_left.add(Div("📊 Analytics", cls="font-bold")) # Footer layout.footer_left.add(Div("© 2024 Analytics Co.")) layout.footer_right.add(Div("v1.0.0")) # Set initial main content layout.set_main(Div(H1("Dashboard"), P("Overview of your metrics"))) ``` ### Example 2: Development Tool with Debug Panel An application with development tools in the right drawer: ```python from fasthtml.common import * from myfasthtml.controls.Layout import Layout from myfasthtml.controls.helpers import mk # Create layout layout = Layout(parent=root_instance, app_name="Dev Tools") # Main content: code editor layout.set_main( Div( H2("Code Editor"), Textarea("# Write your code here", rows=20, cls="w-full font-mono") ) ) # Right drawer: debug and tools layout.right_drawer.add_group("debug", group_ft=Div("DEBUG INFO", cls="font-bold text-xs px-2 opacity-60")) layout.right_drawer.add(Div("Console output here..."), group="debug") layout.right_drawer.add(Div("Variables: x=10, y=20"), group="debug") layout.right_drawer.add_group("tools", group_ft=Div("TOOLS", cls="font-bold text-xs px-2 opacity-60")) layout.right_drawer.add(Button("Run Code"), group="tools") layout.right_drawer.add(Button("Clear Console"), group="tools") # Header layout.header_left.add(Div("DevTools IDE")) layout.header_right.add(Button("Save")) ``` ### Example 3: Minimal Layout (Main Content Only) A simple layout without drawers, focusing only on main content: ```python from fasthtml.common import * from myfasthtml.controls.Layout import Layout # Create layout layout = Layout(parent=root_instance, app_name="Simple Blog") # Header layout.header_left.add(Div("My Blog", cls="text-xl font-bold")) layout.header_right.add(A("About", href="/about")) # Main content layout.set_main( Article( H1("Welcome to My Blog"), P("This is a simple blog layout without side drawers."), P("The focus is on the content in the center.") ) ) # Footer layout.footer_left.add(Div("© 2024 Blog Author")) layout.footer_right.add(A("RSS", href="/rss")) # Note: Drawers are present but can be collapsed by users if not needed ``` ### Example 4: Dynamic Content Loading Loading content dynamically based on user interaction: ```python from fasthtml.common import * from myfasthtml.controls.Layout import Layout from myfasthtml.controls.helpers import mk from myfasthtml.core.commands import Command layout = Layout(parent=root_instance, app_name="Dynamic App") # Function that loads content dynamically def load_page(page_name): # Simulate loading different content content = { "home": Div(H1("Home"), P("Welcome to the home page")), "profile": Div(H1("Profile"), P("User profile information")), "settings": Div(H1("Settings"), P("Application settings")), } layout.set_main(content.get(page_name, Div("Page not found"))) return layout._mk_main() # Create navigation commands pages = ["home", "profile", "settings"] for page in pages: cmd = Command(f"load_{page}", f"Load {page} page", load_page, page) layout.left_drawer.add( mk.mk(Div(page.capitalize()), command=cmd) ) # Set initial content layout.set_main(Div(H1("Home"), P("Welcome to the home page"))) ``` --- ## Developer Reference This section contains technical details for developers working on the Layout component itself. ### State The Layout component maintains the following state properties: | Name | Type | Description | Default | |----------------------|---------|----------------------------------|---------| | `left_drawer_open` | boolean | True if the left drawer is open | True | | `right_drawer_open` | boolean | True if the right drawer is open | True | | `left_drawer_width` | integer | Width of the left drawer | 250 | | `right_drawer_width` | integer | Width of the right drawer | 250 | ### Commands Available commands for programmatic control: | Name | Description | |-----------------------------------------|----------------------------------------------------------------------------------------| | `toggle_drawer(side)` | Toggles the drawer on the specified side | | `update_drawer_width(side, width=None)` | Updates the drawer width on the specified side. The width is given by the HTMX request | ### Public Methods | Method | Description | |---------------------|-----------------------------| | `set_main(content)` | Sets the main content area | | `render()` | Renders the complete layout | ### High Level Hierarchical Structure ``` Div(id="layout") ├── Header │ ├── Div(id="layout_hl") │ │ ├── Icon # Left drawer icon button │ │ └── Div # Left content for the header │ └── Div(id="layout_hr") │ ├── Div # Right content for the header │ └── UserProfile # user profile icon button ├── Div # Left Drawer ├── Main # Main content ├── Div # Right Drawer ├── Footer # Footer └── Script # To initialize the resizing ``` ### Element IDs | Name | Description | |-------------|-------------------------------------| | `layout` | Root layout container (singleton) | | `layout_h` | Header section (not currently used) | | `layout_hl` | Header left side | | `layout_hr` | Header right side | | `layout_f` | Footer section (not currently used) | | `layout_fl` | Footer left side | | `layout_fr` | Footer right side | | `layout_ld` | Left drawer | | `layout_rd` | Right drawer | ### Internal Methods These methods are used internally for rendering: | Method | Description | |---------------------------|--------------------------------------------------------| | `_mk_header()` | Renders the header component | | `_mk_footer()` | Renders the footer component | | `_mk_main()` | Renders the main content area | | `_mk_left_drawer()` | Renders the left drawer | | `_mk_right_drawer()` | Renders the right drawer | | `_mk_left_drawer_icon()` | Renders the left drawer toggle icon | | `_mk_right_drawer_icon()` | Renders the right drawer toggle icon | | `_mk_content_wrapper()` | Static method to wrap content with groups and dividers | ### Content Class The `Layout.Content` nested class manages content zones: | Method | Description | |-----------------------------------|----------------------------------------------------------| | `add(content, group=None)` | Adds content to a group, prevents duplicates based on ID | | `add_group(group, group_ft=None)` | Creates a new group with optional custom header element | | `get_content()` | Returns dictionary of groups and their content | | `get_groups()` | Returns list of (group_name, group_ft) tuples |