Files
MyFastHtml/docs/TabsManager.md

23 KiB
Raw Blame History

TabsManager Component

Introduction

The TabsManager component provides a dynamic tabbed interface for organizing multiple views within your FastHTML application. It handles tab creation, activation, closing, and content management with automatic state persistence and HTMX-powered interactions.

Key features:

  • Dynamic tab creation and removal at runtime
  • Automatic content caching for performance
  • Session-based state persistence (tabs, order, active tab)
  • Duplicate tab detection based on component identity
  • Built-in search menu for quick tab navigation
  • Auto-increment labels for programmatic tab creation
  • HTMX-powered updates without page reload

Common use cases:

  • Multi-document editor (code editor, text editor)
  • Dashboard with multiple data views
  • Settings interface with different configuration panels
  • Developer tools with console, inspector, network tabs
  • Application with dynamic content sections

Quick Start

Here's a minimal example showing a tabbed interface with three views:

from fasthtml.common import *
from myfasthtml.controls.TabsManager import TabsManager
from myfasthtml.core.instances import RootInstance

# Create root instance and tabs manager
root = RootInstance(session)
tabs = TabsManager(parent=root)

# Create three tabs with different content
tabs.create_tab("Dashboard", Div(H1("Dashboard"), P("Overview of your data")))
tabs.create_tab("Settings", Div(H1("Settings"), P("Configure your preferences")))
tabs.create_tab("Profile", Div(H1("Profile"), P("Manage your profile")))

# Render the tabs manager
return tabs

This creates a complete tabbed interface with:

  • A header bar displaying three clickable tab buttons ("Dashboard", "Settings", "Profile")
  • Close buttons (×) on each tab for dynamic removal
  • A main content area showing the active tab's content
  • A search menu (⊞ icon) for quick tab navigation when many tabs are open
  • Automatic HTMX updates when switching or closing tabs

Note: Tabs are interactive by default. Users can click tab labels to switch views, click close buttons to remove tabs, or use the search menu to find tabs quickly. All interactions update the UI without page reload thanks to HTMX integration.

Basic Usage

Visual Structure

The TabsManager component consists of a header with tab buttons and a content area:

┌────────────────────────────────────────────────────────────┐
│  Tab Header                                                │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐          ┌────┐  │
│  │ Tab 1  × │ │ Tab 2  × │ │ Tab 3  × │          │ ⊞  │  │
│  └──────────┘ └──────────┘ └──────────┘          └────┘  │
├────────────────────────────────────────────────────────────┤
│                                                            │
│                                                            │
│                    Active Tab Content                      │
│                                                            │
│                                                            │
└────────────────────────────────────────────────────────────┘

Component details:

Element Description
Tab buttons Clickable labels to switch between tabs
Close button (×) Removes the tab and its content
Search menu (⊞) Dropdown menu to search and filter tabs
Content area Displays the active tab's content

Creating a TabsManager

The TabsManager is a MultipleInstance, meaning you can create multiple independent tab managers in your application. Create it by providing a parent instance:

tabs = TabsManager(parent=root_instance)

# Or with a custom ID
tabs = TabsManager(parent=root_instance, _id="my-tabs")

Creating Tabs

Use the create_tab() method to add a new tab:

# Create a tab with custom content
tab_id = tabs.create_tab(
    label="My Tab",
    component=Div(H1("Content"), P("Tab content here"))
)

# Create with a MyFastHtml control
from myfasthtml.controls.VisNetwork import VisNetwork
network = VisNetwork(parent=tabs, nodes=nodes_data, edges=edges_data)
tab_id = tabs.create_tab("Network View", network)

# Create without activating immediately
tab_id = tabs.create_tab("Background Tab", content, activate=False)

Parameters:

  • label (str): Display text shown in the tab button
  • component (Any): Content to display in the tab (FastHTML elements or MyFastHtml controls)
  • activate (bool): Whether to make this tab active immediately (default: True)

Returns: A unique tab_id (UUID string) that identifies the tab

Showing Tabs

Use the show_tab() method to activate and display a tab:

# Show a tab (makes it active and sends content to client if needed)
tabs.show_tab(tab_id)

# Show without activating (just send content to client)
tabs.show_tab(tab_id, activate=False)

Parameters:

  • tab_id (str): The UUID of the tab to show
  • activate (bool): Whether to make this tab active (default: True)

Note: The first time a tab is shown, its content is sent to the client and cached. Subsequent activations just toggle visibility without re-sending content.

Closing Tabs

Use the close_tab() method to remove a tab:

# Close a specific tab
tabs.close_tab(tab_id)

What happens when closing:

  1. Tab is removed from the tab list and order
  2. Content is removed from cache and client
  3. If the closed tab was active, the first remaining tab becomes active
  4. If no tabs remain, active_tab is set to None

Changing Tab Content

Use the change_tab_content() method to update an existing tab's content and label:

# Update tab content and label
new_content = Div(H1("Updated"), P("New content"))
tabs.change_tab_content(
    tab_id=tab_id,
    label="Updated Tab",
    component=new_content,
    activate=True
)

Parameters:

  • tab_id (str): The UUID of the tab to update
  • label (str): New label for the tab
  • component (Any): New content to display
  • activate (bool): Whether to activate the tab after updating (default: True)

Note: This method forces the new content to be sent to the client, even if the tab was already displayed.

Advanced Features

Auto-increment Labels

When creating multiple tabs programmatically, you can use auto-increment to generate unique labels:

# Using the on_new_tab method with auto_increment
def create_multiple_tabs():
    # Creates "Untitled_0", "Untitled_1", "Untitled_2"
    tabs.on_new_tab("Untitled", content, auto_increment=True)
    tabs.on_new_tab("Untitled", content, auto_increment=True)
    tabs.on_new_tab("Untitled", content, auto_increment=True)

How it works:

  • The TabsManager maintains an internal counter (_tab_count)
  • When auto_increment=True, the counter value is appended to the label
  • Counter increments with each auto-incremented tab creation
  • Useful for "New Tab 1", "New Tab 2" patterns in editors or tools

Duplicate Detection

The TabsManager automatically detects and reuses tabs with identical content to prevent duplicates:

# Create a control instance
network = VisNetwork(parent=tabs, nodes=data, edges=edges)

# First call creates a new tab
tab_id_1 = tabs.create_tab("Network", network)

# Second call with same label and component returns existing tab_id
tab_id_2 = tabs.create_tab("Network", network)

# tab_id_1 == tab_id_2  (True - same tab!)

Detection criteria: A tab is considered a duplicate if all three match:

  • Same label
  • Same component_type (component class prefix)
  • Same component_id (component instance ID)

Note: This only works with BaseInstance components (MyFastHtml controls). Plain FastHTML elements don't have IDs and will always create new tabs.

Dynamic Content Updates

You can update tabs dynamically during the session:

# Initial tab creation
tab_id = tabs.create_tab("Data View", Div("Loading..."))

# Later, update with actual data
def load_data():
    data_content = Div(H2("Data"), P("Loaded content"))
    tabs.change_tab_content(tab_id, "Data View", data_content)
    # Returns HTMX response to update the UI

Use cases:

  • Loading data asynchronously
  • Refreshing tab content based on user actions
  • Updating visualizations with new data
  • Switching between different views in the same tab

Tab Search Menu

The built-in search menu helps users navigate when many tabs are open:

# The search menu is automatically created and includes:
# - A Search control for filtering tabs by label
# - Live filtering as you type
# - Click to activate a tab from search results

How to access:

  • Click the ⊞ icon in the tab header
  • Start typing to filter tabs by label
  • Click a result to activate that tab

The search menu updates automatically when tabs are added or removed.

HTMX Out-of-Band Swaps

For advanced HTMX control, you can customize swap behavior:

# Standard behavior (out-of-band swap enabled)
tabs.show_tab(tab_id, oob=True)  # Default

# Custom target behavior (disable out-of-band)
tabs.show_tab(tab_id, oob=False)  # Swap into HTMX target only

When to use oob=False:

  • When you want to control the exact HTMX target
  • When combining with other HTMX responses
  • When the tab activation is triggered by a command with a specific target

When to use oob=True (default):

  • Most common use case
  • Allows other controls to trigger tab changes without caring about targets
  • Enables automatic UI updates across multiple elements

CSS Customization

The TabsManager uses CSS classes that you can customize:

Class Element
mf-tabs-manager Root tabs manager container
mf-tabs-header-wrapper Header wrapper (buttons + menu)
mf-tabs-header Tab buttons container
mf-tab-button Individual tab button
mf-tab-active Active tab button (modifier)
mf-tab-label Tab label text
mf-tab-close-btn Close button (×)
mf-tab-content-wrapper Content area container
mf-tab-content Individual tab content
mf-empty-content Empty state when no tabs

Example customization:

/* Change active tab color */
.mf-tab-active {
    background-color: #3b82f6;
    color: white;
}

/* Customize close button */
.mf-tab-close-btn:hover {
    color: red;
}

/* Style the content area */
.mf-tab-content-wrapper {
    padding: 2rem;
    background-color: #f9fafb;
}

Examples

Example 1: Multi-view Application

A typical application with different views accessible through tabs:

from fasthtml.common import *
from myfasthtml.controls.TabsManager import TabsManager
from myfasthtml.core.instances import RootInstance

# Create tabs manager
root = RootInstance(session)
tabs = TabsManager(parent=root, _id="app-tabs")

# Dashboard view
dashboard = Div(
    H1("Dashboard"),
    Div(
        Div("Total Users: 1,234", cls="stat"),
        Div("Active Sessions: 56", cls="stat"),
        Div("Revenue: $12,345", cls="stat"),
        cls="stats-grid"
    )
)

# Analytics view
analytics = Div(
    H1("Analytics"),
    P("Detailed analytics and reports"),
    Div("Chart placeholder", cls="chart-container")
)

# Settings view
settings = Div(
    H1("Settings"),
    Form(
        Label("Username:", Input(name="username", value="admin")),
        Label("Email:", Input(name="email", value="admin@example.com")),
        Button("Save", type="submit"),
    )
)

# Create tabs
tabs.create_tab("Dashboard", dashboard)
tabs.create_tab("Analytics", analytics)
tabs.create_tab("Settings", settings)

# Render
return tabs

Example 2: Dynamic Tabs with VisNetwork

Creating tabs dynamically with interactive network visualizations:

from fasthtml.common import *
from myfasthtml.controls.TabsManager import TabsManager
from myfasthtml.controls.VisNetwork import VisNetwork
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
from myfasthtml.core.instances import RootInstance

root = RootInstance(session)
tabs = TabsManager(parent=root, _id="network-tabs")

# Create initial tab with welcome message
tabs.create_tab("Welcome", Div(
    H1("Network Visualizer"),
    P("Click 'Add Network' to create a new network visualization")
))

# Function to create a new network tab
def add_network_tab():
    # Define network data
    nodes = [
        {"id": 1, "label": "Node 1"},
        {"id": 2, "label": "Node 2"},
        {"id": 3, "label": "Node 3"}
    ]
    edges = [
        {"from": 1, "to": 2},
        {"from": 2, "to": 3}
    ]

    # Create network instance
    network = VisNetwork(parent=tabs, nodes=nodes, edges=edges)

    # Use auto-increment to create unique labels
    return tabs.on_new_tab("Network", network, auto_increment=True)

# Create command for adding networks
add_cmd = Command("add_network", "Add network tab", add_network_tab)

# Add button to create new network tabs
add_button = mk.button("Add Network", command=add_cmd, cls="btn btn-primary")

# Return tabs and button
return Div(add_button, tabs)

Example 3: Tab Management with Content Updates

An application that updates tab content based on user interaction:

from fasthtml.common import *
from myfasthtml.controls.TabsManager import TabsManager
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
from myfasthtml.core.instances import RootInstance

root = RootInstance(session)
tabs = TabsManager(parent=root, _id="editor-tabs")

# Create initial document tabs
doc1_id = tabs.create_tab("Document 1", Textarea("Initial content 1", rows=10))
doc2_id = tabs.create_tab("Document 2", Textarea("Initial content 2", rows=10))

# Function to refresh a document's content
def refresh_document(tab_id, doc_name):
    # Simulate loading new content
    new_content = Textarea(f"Refreshed content for {doc_name}\nTimestamp: {datetime.now()}", rows=10)
    tabs.change_tab_content(tab_id, doc_name, new_content)
    return tabs._mk_tabs_controller(oob=True), tabs._mk_tabs_header_wrapper(oob=True)

# Create refresh commands
refresh_doc1 = Command("refresh_1", "Refresh doc 1", refresh_document, doc1_id, "Document 1")
refresh_doc2 = Command("refresh_2", "Refresh doc 2", refresh_document, doc2_id, "Document 2")

# Add refresh buttons
controls = Div(
    mk.button("Refresh Document 1", command=refresh_doc1, cls="btn btn-sm"),
    mk.button("Refresh Document 2", command=refresh_doc2, cls="btn btn-sm"),
    cls="controls-bar"
)

return Div(controls, tabs)

Example 4: Using Auto-increment for Dynamic Tabs

Creating multiple tabs programmatically with auto-generated labels:

from fasthtml.common import *
from myfasthtml.controls.TabsManager import TabsManager
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
from myfasthtml.core.instances import RootInstance

root = RootInstance(session)
tabs = TabsManager(parent=root, _id="dynamic-tabs")

# Create initial placeholder tab
tabs.create_tab("Start", Div(
    H2("Welcome"),
    P("Click 'New Tab' to create numbered tabs")
))

# Function to create a new numbered tab
def create_numbered_tab():
    content = Div(
        H2("New Tab Content"),
        P(f"This tab was created dynamically"),
        Input(placeholder="Enter some text...", cls="input")
    )
    # Auto-increment creates "Tab_0", "Tab_1", "Tab_2", etc.
    return tabs.on_new_tab("Tab", content, auto_increment=True)

# Create command
new_tab_cmd = Command("new_tab", "Create new tab", create_numbered_tab)

# Add button
new_tab_button = mk.button("New Tab", command=new_tab_cmd, cls="btn btn-primary")

return Div(
    Div(new_tab_button, cls="toolbar"),
    tabs
)

Developer Reference

This section contains technical details for developers working on the TabsManager component itself.

State

The TabsManager component maintains the following state properties:

Name Type Description Default
tabs dict[str, Any] Dictionary of tab metadata (id, label, component) {}
tabs_order list[str] Ordered list of tab IDs []
active_tab str | None ID of the currently active tab None
ns_tabs_content dict[str, Any] Cache of tab content (raw, not wrapped) {}
ns_tabs_sent_to_client set Set of tab IDs already sent to client set()

Note: Properties prefixed with ns_ are not persisted in the database and exist only for the session.

Commands

Available commands for programmatic control:

Name Description
show_tab(tab_id) Activate or show a specific tab
close_tab(tab_id) Close a specific tab
add_tab(label, component, auto_increment) Add a new tab with optional auto-increment

Public Methods

Method Description
create_tab(label, component, activate=True) Create a new tab or reuse existing duplicate
show_tab(tab_id, activate=True, oob=True) Send tab to client and/or activate it
close_tab(tab_id) Close and remove a tab
change_tab_content(tab_id, label, component, activate=True) Update existing tab's label and content
on_new_tab(label, component, auto_increment=False) Create and show tab with auto-increment support
add_tab_btn() Returns add tab button element
get_state() Returns the TabsManagerState object
render() Renders the complete TabsManager component

High Level Hierarchical Structure

Div(id="{id}", cls="mf-tabs-manager")
├── Div(id="{id}-controller")                 # Controller (hidden, manages active state)
├── Div(id="{id}-header-wrapper")             # Header wrapper
│   ├── Div(id="{id}-header")                 # Tab buttons container
│   │   ├── Div (mf-tab-button)               # Tab button 1
│   │   │   ├── Span (mf-tab-label)           # Label (clickable)
│   │   │   └── Span (mf-tab-close-btn)       # Close button
│   │   ├── Div (mf-tab-button)               # Tab button 2
│   │   └── ...
│   └── Div (dropdown)                        # Search menu
│       ├── Icon (tabs24_regular)             # Menu toggle button
│       └── Div (dropdown-content)            # Search component
├── Div(id="{id}-content-wrapper")            # Content wrapper
│   ├── Div(id="{id}-{tab_id_1}-content")     # Tab 1 content
│   ├── Div(id="{id}-{tab_id_2}-content")     # Tab 2 content
│   └── ...
└── Script                                     # Initialization script

Element IDs

Name Description
{id} Root tabs manager container
{id}-controller Hidden controller managing active state
{id}-header-wrapper Header wrapper (buttons + search)
{id}-header Tab buttons container
{id}-content-wrapper Content area wrapper
{id}-{tab_id}-content Individual tab content
{id}-search Search component ID

Note: {id} is the TabsManager instance ID, {tab_id} is the UUID of each tab.

Internal Methods

These methods are used internally for rendering:

Method Description
_mk_tabs_controller(oob=False) Renders the hidden controller element
_mk_tabs_header_wrapper(oob=False) Renders the header wrapper with buttons and search
_mk_tab_button(tab_data) Renders a single tab button
_mk_tab_content_wrapper() Renders the content wrapper with active tab content
_mk_tab_content(tab_id, content) Renders individual tab content div
_mk_show_tabs_menu() Renders the search dropdown menu
_wrap_tab_content(tab_content) Wraps tab content for HTMX out-of-band insertion
_get_or_create_tab_content(tab_id) Gets tab content from cache or creates it
_dynamic_get_content(tab_id) Retrieves component from InstancesManager
_tab_already_exists(label, component) Checks if duplicate tab exists
_add_or_update_tab(...) Internal method to add/update tab in state
_get_ordered_tabs() Returns tabs ordered by tabs_order list
_get_tab_list() Returns list of tab dictionaries in order
_get_tab_count() Returns and increments internal tab counter

Tab Metadata Structure

Each tab in the tabs dictionary has the following structure:

{
    'id': 'uuid-string',              # Unique tab identifier
    'label': 'Tab Label',             # Display label
    'component_type': 'prefix',       # Component class prefix (or None)
    'component_id': 'instance-id'     # Component instance ID (or None)
}

Note: component_type and component_id are None for plain FastHTML elements that don't inherit from BaseInstance.