Skip to content

API Reference

This section provides automatically generated documentation from the FastStrap source code. It is useful for looking up exact parameter names and types.

Core Utilities

faststrap.core.theme.resolve_defaults(component, **kwargs)

Resolve component attributes by merging defaults with user arguments.

Priority (highest to lowest): 1. Explicit user arguments (including None when passed intentionally) 2. Global component defaults (set via set_component_defaults)

Parameters:

Name Type Description Default
component str

Component name (e.g., "Button")

required
**kwargs Any

Arguments passed by the user

{}

Returns:

Type Description
dict[str, Any]

Dict of resolved attributes

Examples:

>>> set_component_defaults("Button", variant="secondary")
>>> resolve_defaults("Button", variant=UNSET, size="lg")
{"variant": "secondary", "size": "lg"}
>>> resolve_defaults("Button", variant=None)
{"variant": None, "size": None, "outline": False}
Source code in src/faststrap/core/theme.py
def resolve_defaults(component: str, **kwargs: Any) -> dict[str, Any]:
    """Resolve component attributes by merging defaults with user arguments.

    Priority (highest to lowest):
    1. Explicit user arguments (including None when passed intentionally)
    2. Global component defaults (set via set_component_defaults)

    Args:
        component: Component name (e.g., "Button")
        **kwargs: Arguments passed by the user

    Returns:
        Dict of resolved attributes

    Examples:
        >>> set_component_defaults("Button", variant="secondary")
        >>> resolve_defaults("Button", variant=UNSET, size="lg")
        {"variant": "secondary", "size": "lg"}
        >>> resolve_defaults("Button", variant=None)
        {"variant": None, "size": None, "outline": False}
    """
    global _COMPONENT_DEFAULTS_LOCKED

    with _COMPONENT_DEFAULTS_LOCK:
        _COMPONENT_DEFAULTS_LOCKED = True
        defaults = _COMPONENT_DEFAULTS.get(component, {}).copy()

    resolved = defaults.copy()

    for key, value in kwargs.items():
        if value is not UNSET:
            resolved[key] = value
    return resolved

faststrap.core.base.merge_classes(*class_lists)

Merge multiple class strings or lists, removing duplicates.

Source code in src/faststrap/core/base.py
def merge_classes(*class_lists: Any) -> str:
    """Merge multiple class strings or lists, removing duplicates."""
    classes: list[str] = []
    seen = set()

    def _process(item: Any) -> None:
        if not item:
            return
        if isinstance(item, (list, tuple)):
            for sub in item:
                _process(sub)
        elif isinstance(item, str):
            for cls in item.split():
                cls = cls.strip()
                if cls and cls not in seen:
                    classes.append(cls)
                    seen.add(cls)

    for item in class_lists:
        _process(item)

    return " ".join(classes)

Attributes Helper

Use convert_attrs() when authoring custom FastStrap components or wrappers. It converts Python-friendly kwargs such as hx_get, data_bs_toggle, aria_label, style={...}, and css_vars={...} into valid HTML attributes.

from faststrap import convert_attrs

attrs = {"cls": "btn btn-primary"}
attrs.update(
    convert_attrs(
        {
            "hx_post": "/save",
            "hx_target": "#result",
            "data": {"mode": "inline"},
            "css_vars": {"brand_color": "#5B6CFF"},
        }
    )
)

For component authors, routing **kwargs through convert_attrs(kwargs) is the standard way to preserve HTMX support and attribute conversion consistently.

faststrap.utils.attrs.convert_attrs(kwargs)

Convert Python kwargs to HTML attributes (hx_get -> hx-get).

Rules: - None values are dropped - boolean False values are dropped (except aria_*, which are preserved) - boolean True values are kept (FastHTML will serialize appropriately) - style may be a str or a dict (dict is serialized) - css_vars may be a dict and will be merged into style - data={...} expands to data-* - aria={...} expands to aria-*

Parameters:

Name Type Description Default
kwargs dict[str, Any]

Python-style keyword arguments

required

Returns:

Type Description
dict[str, Any]

HTML-style attributes with hyphens

Source code in src/faststrap/utils/attrs.py
def convert_attrs(kwargs: dict[str, Any]) -> dict[str, Any]:
    """Convert Python kwargs to HTML attributes (hx_get -> hx-get).

    Rules:
    - `None` values are dropped
    - boolean `False` values are dropped (except `aria_*`, which are preserved)
    - boolean `True` values are kept (FastHTML will serialize appropriately)
    - `style` may be a `str` or a `dict` (dict is serialized)
    - `css_vars` may be a dict and will be merged into `style`
    - `data={...}` expands to `data-*`
    - `aria={...}` expands to `aria-*`

    Args:
        kwargs: Python-style keyword arguments

    Returns:
        HTML-style attributes with hyphens
    """
    converted: dict[str, Any] = {}

    # ---- Extract style/css_vars/data/aria ---------------------------------
    style_val = kwargs.get("style")
    css_vars_val = kwargs.get("css_vars")
    data_val = kwargs.get("data")
    aria_val = kwargs.get("aria")

    style_str: str | None = None

    if isinstance(style_val, dict):
        style_str = _style_to_string(style_val)
    elif isinstance(style_val, str):
        style_str = style_val.strip() or None
    elif style_val is None:
        style_str = None

    if isinstance(css_vars_val, dict):
        css_style: dict[str, Any] = {}
        for k, v in css_vars_val.items():
            if v is None:
                continue
            key = str(k)
            if not key.startswith("--"):
                key = f"--{_css_key(key)}"
            css_style[key] = v
        style_str = _merge_style(style_str, _style_to_string(css_style))

    # ---- Convert regular attributes ---------------------------------------
    for k, v in kwargs.items():
        if k in {"style", "css_vars", "data", "aria"}:
            continue

        # Keep cls as-is (FastHTML convention)
        if k == "cls":
            converted[k] = v
            continue

        # Normalize aria booleans as strings (preferred for HTML output)
        if k.startswith("aria_") and isinstance(v, bool):
            converted[_to_kebab(k)] = "true" if v else "false"
            continue

        # Drop None and False
        if v is None:
            continue
        if isinstance(v, bool) and v is False:
            continue

        converted[_to_kebab(k)] = v

    # ---- Expand structured data/aria dicts --------------------------------
    if isinstance(data_val, dict):
        for dk, dv in data_val.items():
            if dv is None:
                continue
            if isinstance(dv, bool):
                converted[f"data-{_to_kebab(str(dk))}"] = "true" if dv else "false"
            else:
                converted[f"data-{_to_kebab(str(dk))}"] = _stringify_attr_value(dv)

    if isinstance(aria_val, dict):
        for ak, av in aria_val.items():
            if av is None:
                continue
            if isinstance(av, bool):
                converted[f"aria-{_to_kebab(str(ak))}"] = "true" if av else "false"
            else:
                converted[f"aria-{_to_kebab(str(ak))}"] = _stringify_attr_value(av)

    # ---- Apply merged style if present ------------------------------------
    if style_str:
        converted["style"] = style_str

    return converted

Application Setup

faststrap.core.assets.add_bootstrap(app, theme=None, mode='light', use_cdn=None, mount_static=True, static_url='/static', force_static_url=False, include_favicon=True, favicon_url=None, font_family=None, font_weights=None, components=None)

Enhance FastHTML app with Bootstrap and FastStrap assets.

Parameters:

Name Type Description Default
app Any

FastHTML application instance

required
theme str | Theme | None

Color theme - either a built-in name (e.g., "green-nature", "purple-magic"), a Theme instance created via create_theme(), or a community theme

None
mode ModeType

Color mode for light/dark backgrounds: - "light": Light background, dark text (default) - "dark": Dark background, light text - "auto": Follows user's system preference (prefers-color-scheme)

'light'
use_cdn bool | None

When True, ALL assets (Bootstrap CSS/JS, Bootstrap Icons, Faststrap CSS files, favicon) are served from CDN. No local StaticFiles are mounted. Required for serverless deployments (Vercel, AWS Lambda, Google Cloud Run). Default: False.

None
mount_static bool

Auto-mount static directory

True
static_url str

Preferred URL prefix for static files

'/static'
force_static_url bool

Force use of this URL even if already mounted

False
include_favicon bool

Include default FastStrap favicon

True
favicon_url str | None

Custom favicon URL (overrides default)

None
font_family str | None

Google Font name (e.g., "Inter", "Roboto", "Poppins")

None
font_weights list[int] | None

Font weights to load (default: [400, 500, 700])

None
components list[Any] | None

Optional list of Faststrap component functions used in the app. When provided, Bootstrap JS is only injected if at least one component has requires_js=True in its registry metadata. Components without @register() metadata are treated as requires_js=False. When None (default), JS is always injected.

None

Returns:

Type Description
Any

Modified app instance

Examples:

Basic setup with light mode

add_bootstrap(app)

Dark mode with a color theme

add_bootstrap(app, theme="purple-magic", mode="dark")

Auto mode (follows system preference)

add_bootstrap(app, theme="green-nature", mode="auto")

Custom theme with dark mode

from faststrap import create_theme my_theme = create_theme(primary="#7BA05B", secondary="#48C774") add_bootstrap(app, theme=my_theme, mode="dark")

Built-in theme with custom font

add_bootstrap(app, theme="green-nature", font_family="Inter")

Custom theme with custom font

my_theme = create_theme(primary="#7BA05B") add_bootstrap(app, theme=my_theme, font_family="Roboto", font_weights=[400, 600, 700])

Font only, no theme

add_bootstrap(app, font_family="Poppins")

CDN mode for production

add_bootstrap(app, theme="blue-ocean", mode="auto", use_cdn=True)

Source code in src/faststrap/core/assets.py
def add_bootstrap(
    app: Any,
    theme: str | Theme | None = None,
    mode: ModeType = "light",
    use_cdn: bool | None = None,
    mount_static: bool = True,
    static_url: str = "/static",
    force_static_url: bool = False,
    include_favicon: bool = True,
    favicon_url: str | None = None,
    font_family: str | None = None,
    font_weights: list[int] | None = None,
    components: list[Any] | None = None,
) -> Any:
    """Enhance FastHTML app with Bootstrap and FastStrap assets.

    Args:
        app: FastHTML application instance
        theme: Color theme - either a built-in name (e.g., "green-nature", "purple-magic"),
               a Theme instance created via create_theme(), or a community theme
        mode: Color mode for light/dark backgrounds:
              - "light": Light background, dark text (default)
              - "dark": Dark background, light text
              - "auto": Follows user's system preference (prefers-color-scheme)
        use_cdn: When True, ALL assets (Bootstrap CSS/JS, Bootstrap Icons,
                 Faststrap CSS files, favicon) are served from CDN. No local
                 StaticFiles are mounted. Required for serverless deployments
                 (Vercel, AWS Lambda, Google Cloud Run). Default: False.
        mount_static: Auto-mount static directory
        static_url: Preferred URL prefix for static files
        force_static_url: Force use of this URL even if already mounted
        include_favicon: Include default FastStrap favicon
        favicon_url: Custom favicon URL (overrides default)
        font_family: Google Font name (e.g., "Inter", "Roboto", "Poppins")
        font_weights: Font weights to load (default: [400, 500, 700])
        components: Optional list of Faststrap component functions used in the app.
            When provided, Bootstrap JS is only injected if at least one
            component has requires_js=True in its registry metadata.
            Components without @register() metadata are treated as
            requires_js=False. When None (default), JS is always injected.

    Returns:
        Modified app instance

    Examples:
        # Basic setup with light mode
        add_bootstrap(app)

        # Dark mode with a color theme
        add_bootstrap(app, theme="purple-magic", mode="dark")

        # Auto mode (follows system preference)
        add_bootstrap(app, theme="green-nature", mode="auto")

        # Custom theme with dark mode
        from faststrap import create_theme
        my_theme = create_theme(primary="#7BA05B", secondary="#48C774")
        add_bootstrap(app, theme=my_theme, mode="dark")

        # Built-in theme with custom font
        add_bootstrap(app, theme="green-nature", font_family="Inter")

        # Custom theme with custom font
        my_theme = create_theme(primary="#7BA05B")
        add_bootstrap(app, theme=my_theme, font_family="Roboto", font_weights=[400, 600, 700])

        # Font only, no theme
        add_bootstrap(app, font_family="Poppins")

        # CDN mode for production
        add_bootstrap(app, theme="blue-ocean", mode="auto", use_cdn=True)
    """
    if getattr(app, "_faststrap_bootstrap_added", False):
        warnings.warn(
            "add_bootstrap() has already been called on this app. " "Subsequent calls are ignored.",
            RuntimeWarning,
            stacklevel=2,
        )
        return
    if use_cdn is None:
        use_cdn = environ.get("FASTSTRAP_USE_CDN", "false").lower() == "true"
    include_js = True if components is None else _any_requires_js(components)

    # 1. Determine where to mount static files
    actual_static_url = static_url
    if not use_cdn and mount_static:
        if force_static_url:
            actual_static_url = static_url
        else:
            # Only resolve and mount if not already done
            if hasattr(app, "_faststrap_static_url"):
                actual_static_url = app._faststrap_static_url
            else:
                actual_static_url = resolve_static_url(app, static_url)

    # 2. Collect favicon links FIRST (before Bootstrap assets)
    favicon_links: list[Any] = []
    if favicon_url:
        favicon_links = create_favicon_links(favicon_url)
    elif include_favicon:
        if not use_cdn:
            default_favicon = get_default_favicon_url(False, actual_static_url)
            favicon_links = create_favicon_links(default_favicon)

    # 3. Get Bootstrap assets with theme, mode, and font
    bootstrap_assets = get_assets(
        use_cdn=use_cdn,
        include_custom=True,
        static_url=actual_static_url if not use_cdn else None,
        theme=theme,
        mode=mode,
        font_family=font_family,
        font_weights=font_weights,
        include_js=include_js,
        include_favicon=use_cdn and include_favicon and favicon_url is None,
    )

    # 4. Idempotent Header Management
    # Remove any existing FastStrap headers to prevent accumulation
    new_fs_hdrs = list(favicon_links) + list(bootstrap_assets)
    old_fs_hdrs = getattr(app, "_faststrap_hdrs", [])

    current_hdrs = list(getattr(app, "hdrs", []))

    # Remove old items by identity if possible
    filtered_hdrs = [h for h in current_hdrs if h not in old_fs_hdrs]

    # Prepend new ones
    app.hdrs = new_fs_hdrs + filtered_hdrs
    app._faststrap_hdrs = new_fs_hdrs

    # 5. Apply data-bs-theme attribute for non-auto modes
    if mode in {"light", "dark"}:
        existing_htmlkw = getattr(app, "htmlkw", {}) or {}
        existing_htmlkw.update({"data-bs-theme": mode})
        app.htmlkw = existing_htmlkw

    # 6. Mount static files (once only)
    if not use_cdn and mount_static and not hasattr(app, "_faststrap_static_url"):
        try:
            static_path = get_static_path()
            # Use insert(0) to ensure static route takes precedence over catch-all routes (like fast_app's /{path})
            app.routes.insert(
                0,
                Mount(
                    actual_static_url,
                    StaticFiles(directory=str(static_path)),
                    name="faststrap_static",
                ),
            )
            app._faststrap_static_url = actual_static_url
        except Exception as e:
            # Check if this is a "already mounted" error (which is fine)
            error_msg = str(e).lower()
            if any(
                keyword in error_msg for keyword in ["already", "duplicate", "mounted", "exists"]
            ):
                # Static files already mounted by another call, just mark it
                app._faststrap_static_url = actual_static_url
            else:
                # Real error - fall back to CDN
                caution = f"""
            FastStrap: Could not mount local static files ({e}).
            Falling back to CDN mode. You can explicitly set use_cdn=True.
            """
                warnings.warn(caution, RuntimeWarning, stacklevel=2)
                use_cdn = True
                if favicon_url:
                    fallback_favicon_links = create_favicon_links(favicon_url)
                else:
                    fallback_favicon_links = []

                fallback_bootstrap_assets = get_assets(
                    use_cdn=True,
                    include_custom=True,
                    static_url=None,
                    theme=theme,
                    mode=mode,
                    font_family=font_family,
                    font_weights=font_weights,
                    include_js=include_js,
                    include_favicon=include_favicon and favicon_url is None,
                )
                fallback_fs_hdrs = list(fallback_favicon_links) + list(fallback_bootstrap_assets)
                app.hdrs = fallback_fs_hdrs + filtered_hdrs
                app._faststrap_hdrs = fallback_fs_hdrs

    app._faststrap_bootstrap_added = True
    return app

faststrap.core.assets.get_assets(use_cdn=None, include_custom=True, static_url=None, theme=None, mode='light', font_family=None, font_weights=None, include_js=True, include_favicon=False)

Get Bootstrap assets for injection.

Parameters:

Name Type Description Default
use_cdn bool | None

Use CDN (True) or local files (False)

None
include_custom bool

Include FastStrap custom styles

True
static_url str | None

Custom static URL (if using local assets)

None
theme str | Theme | None

Theme name (str) or Theme instance

None
mode ModeType

Color mode - "light", "dark", or "auto"

'light'
font_family str | None

Google Font name (e.g., "Inter", "Roboto", "Poppins")

None
font_weights list[int] | None

Font weights to load (default: [400, 500, 700])

None
include_js bool

Include Bootstrap JavaScript bundle

True
include_favicon bool

Include default Faststrap favicon (CDN mode only)

False

Returns:

Type Description
tuple[Any, ...]

Tuple of FastHTML elements for app.hdrs

Source code in src/faststrap/core/assets.py
def get_assets(
    use_cdn: bool | None = None,
    include_custom: bool = True,
    static_url: str | None = None,
    theme: str | Theme | None = None,
    mode: ModeType = "light",
    font_family: str | None = None,
    font_weights: list[int] | None = None,
    include_js: bool = True,
    include_favicon: bool = False,
) -> tuple[Any, ...]:
    """
    Get Bootstrap assets for injection.

    Args:
        use_cdn: Use CDN (True) or local files (False)
        include_custom: Include FastStrap custom styles
        static_url: Custom static URL (if using local assets)
        theme: Theme name (str) or Theme instance
        mode: Color mode - "light", "dark", or "auto"
        font_family: Google Font name (e.g., "Inter", "Roboto", "Poppins")
        font_weights: Font weights to load (default: [400, 500, 700])
        include_js: Include Bootstrap JavaScript bundle
        include_favicon: Include default Faststrap favicon (CDN mode only)

    Returns:
        Tuple of FastHTML elements for app.hdrs
    """
    if use_cdn is None:
        use_cdn = environ.get("FASTSTRAP_USE_CDN", "false").lower() == "true"

    if use_cdn:
        assets = tuple(
            _build_cdn_assets(
                _get_faststrap_cdn_version(),
                include_favicon=include_favicon,
                include_js=include_js,
            )
        )
    else:
        actual_static_url = static_url if static_url is not None else "/static"
        assets = local_assets(actual_static_url, include_js=include_js)

    elements = list(assets)

    # Add Google Fonts link if specified (BEFORE other styles for proper loading)
    if font_family:
        weights = font_weights or [400, 500, 700]
        weights_str = ";".join(str(w) for w in weights)
        # Properly encode font family name for URL (handles spaces and special characters)
        encoded_family = quote(font_family)
        font_url = f"https://fonts.googleapis.com/css2?family={encoded_family}:wght@{weights_str}&display=swap"
        # Add preconnect for performance
        elements.insert(0, Link(rel="preconnect", href="https://fonts.googleapis.com"))
        elements.insert(
            1, Link(rel="preconnect", href="https://fonts.gstatic.com", crossorigin=True)
        )
        elements.insert(2, Link(rel="stylesheet", href=font_url))

    if include_custom:
        elements.append(CUSTOM_STYLES)
        elements.append(INIT_SCRIPT)

    # Add theme styles
    if theme is not None:
        if isinstance(theme, str):
            theme_obj = get_builtin_theme(theme)
        elif isinstance(theme, Theme):
            theme_obj = theme
        else:
            raise ValueError("theme must be a string (theme name) or Theme instance")
        elements.append(theme_obj.to_style(mode=mode))

    # Add font-family CSS if font specified (AFTER theme so it can override)
    if font_family:
        font_css = Style(
            f":root {{ --bs-body-font-family: '{font_family}', sans-serif; }} "
            f"body {{ font-family: var(--bs-body-font-family); }}"
        )
        elements.append(font_css)

    return tuple(elements)

faststrap.core.assets.mount_assets(app, directory, url_path='/assets', name=None, priority=True, allow_override=False, base_dir=None)

Mount a static files directory to your FastHTML app.

This is a convenience wrapper around Starlette's Mount and StaticFiles that handles path resolution and mounting order automatically.

Parameters:

Name Type Description Default
app Any

FastHTML application instance

required
directory str

Path to directory containing static files. Can be absolute, relative to base_dir, relative to the calling file (when available), or relative to the current working directory as a final fallback.

required
url_path str

URL path to mount at (default: "/assets"). Must start with "/".

'/assets'
name str | None

Mount name for Starlette routing. If None, auto-generated from url_path.

None
priority bool

If True, insert at start of routes to take precedence over catch-all routes (default: True).

True
allow_override bool

If True, allow overriding Faststrap's static mount. NOT recommended as it will break Bootstrap CSS/JS loading. (default: False)

False
base_dir str | PathLike[str] | None

Optional explicit base directory for resolving relative directory paths. When omitted, Faststrap attempts to resolve relative to the calling file and falls back to the current working directory.

None

Raises:

Type Description
ValueError

If url_path doesn't start with "/" or conflicts with Faststrap

FileNotFoundError

If directory doesn't exist

Warning

Do not use the same url_path as Faststrap's static files (usually "/static"). This will cause Bootstrap CSS/JS to fail loading. Use "/assets" or another path.

Examples:

Basic usage:

>>> from fasthtml.common import FastHTML
>>> from faststrap import add_bootstrap, mount_assets
>>>
>>> app = FastHTML()
>>> add_bootstrap(app)
>>> mount_assets(app, "assets")  # Mounts assets/ at /assets/

Multiple directories:

>>> mount_assets(app, "images", url_path="/img")
>>> mount_assets(app, "uploads", url_path="/uploads")

Absolute path:

>>> mount_assets(app, "/var/www/static", url_path="/static-files")

Custom static URL for Faststrap (to use /static for your files):

>>> add_bootstrap(app, static_url="/faststrap-static")
>>> mount_assets(app, "static", url_path="/static")  # Now safe!
Source code in src/faststrap/core/assets.py
def mount_assets(
    app: Any,
    directory: str,
    url_path: str = "/assets",
    name: str | None = None,
    priority: bool = True,
    allow_override: bool = False,
    base_dir: str | os.PathLike[str] | None = None,
) -> None:
    """Mount a static files directory to your FastHTML app.

    This is a convenience wrapper around Starlette's Mount and StaticFiles
    that handles path resolution and mounting order automatically.

    Args:
        app: FastHTML application instance
        directory: Path to directory containing static files.
                  Can be absolute, relative to `base_dir`, relative to the
                  calling file (when available), or relative to the current
                  working directory as a final fallback.
        url_path: URL path to mount at (default: "/assets").
                 Must start with "/".
        name: Mount name for Starlette routing.
             If None, auto-generated from url_path.
        priority: If True, insert at start of routes to take precedence
                 over catch-all routes (default: True).
        allow_override: If True, allow overriding Faststrap's static mount.
                       NOT recommended as it will break Bootstrap CSS/JS loading.
                       (default: False)
        base_dir: Optional explicit base directory for resolving relative
                  `directory` paths. When omitted, Faststrap attempts to
                  resolve relative to the calling file and falls back to the
                  current working directory.

    Raises:
        ValueError: If url_path doesn't start with "/" or conflicts with Faststrap
        FileNotFoundError: If directory doesn't exist

    Warning:
        Do not use the same url_path as Faststrap's static files (usually "/static").
        This will cause Bootstrap CSS/JS to fail loading. Use "/assets" or another path.

    Examples:
        Basic usage:
        >>> from fasthtml.common import FastHTML
        >>> from faststrap import add_bootstrap, mount_assets
        >>>
        >>> app = FastHTML()
        >>> add_bootstrap(app)
        >>> mount_assets(app, "assets")  # Mounts assets/ at /assets/

        Multiple directories:
        >>> mount_assets(app, "images", url_path="/img")
        >>> mount_assets(app, "uploads", url_path="/uploads")

        Absolute path:
        >>> mount_assets(app, "/var/www/static", url_path="/static-files")

        Custom static URL for Faststrap (to use /static for your files):
        >>> add_bootstrap(app, static_url="/faststrap-static")
        >>> mount_assets(app, "static", url_path="/static")  # Now safe!
    """
    # Validate url_path
    if not url_path.startswith("/"):
        raise ValueError(f"url_path must start with '/'. Got: {url_path}")

    # Check for conflicts with Faststrap's static mount
    faststrap_url = getattr(app, "_faststrap_static_url", None)
    if faststrap_url and url_path.rstrip("/") == faststrap_url.rstrip("/"):
        if not allow_override:
            raise ValueError(
                f"Cannot mount assets at '{url_path}' - this conflicts with Faststrap's static files.\n"
                f"Faststrap is using '{faststrap_url}' for Bootstrap CSS/JS.\n\n"
                f"Solutions:\n"
                f"1. Use a different url_path:\n"
                f"   mount_assets(app, '{directory}', url_path='/assets')\n\n"
                f"2. Configure Faststrap to use a different URL:\n"
                f"   add_bootstrap(app, static_url='/faststrap-static')\n"
                f"   mount_assets(app, '{directory}', url_path='/static')\n\n"
                f"3. Override Faststrap (NOT recommended, will break Bootstrap):\n"
                f"   mount_assets(app, '{directory}', url_path='{url_path}', allow_override=True)\n\n"
                f"Recommended: Use '/assets' for your files and '{faststrap_url}' for Faststrap."
            )
        else:
            warnings.warn(
                f"Overriding Faststrap's static mount at '{url_path}'. "
                f"Bootstrap CSS/JS may not load correctly.",
                RuntimeWarning,
                stacklevel=2,
            )

    # Resolve directory path
    if os.path.isabs(directory):
        # Absolute path - use as-is
        assets_path = Path(directory)
    else:
        assets_path = _resolve_relative_assets_path(directory, base_dir=base_dir)

    # Check if directory exists
    if not assets_path.exists():
        raise FileNotFoundError(
            f"Static directory not found: {assets_path}\n"
            f"Make sure the directory exists before calling mount_assets()."
        )

    if not assets_path.is_dir():
        raise ValueError(
            f"Path is not a directory: {assets_path}\n"
            f"mount_assets() requires a directory, not a file."
        )

    # Auto-generate name if not provided
    if name is None:
        # Convert /assets to "assets", /my-files to "my_files"
        name = url_path.strip("/").replace("-", "_").replace("/", "_")
        if not name:
            name = "static"

    # Create the mount
    mount = Mount(url_path, StaticFiles(directory=str(assets_path)), name=name)

    # Add to routes
    if priority:
        # Insert at beginning to take precedence
        app.routes.insert(0, mount)
    else:
        # Append to end
        app.routes.append(mount)

faststrap.utils.static_management.get_faststrap_static_url(app)

Get the URL where FastStrap static files are mounted for this specific app.

Parameters:

Name Type Description Default
app Any

The FastHTML app instance

required

Returns:

Type Description
str | None

The static URL if mounted, None otherwise

Source code in src/faststrap/utils/static_management.py
def get_faststrap_static_url(app: Any) -> str | None:
    """
    Get the URL where FastStrap static files are mounted for this specific app.

    Args:
        app: The FastHTML app instance

    Returns:
        The static URL if mounted, None otherwise
    """
    return getattr(app, "_faststrap_static_url", None)

faststrap.utils.static_management.cleanup_static_resources()

Clean up extracted static resources.

Source code in src/faststrap/utils/static_management.py
def cleanup_static_resources() -> None:
    """Clean up extracted static resources."""
    global _APP_STATIC_STACK, _EXTRACTED_STATIC_PATH, _ATEXIT_REGISTERED
    if _APP_STATIC_STACK:
        try:
            _APP_STATIC_STACK.close()
        finally:
            _APP_STATIC_STACK = None
            _EXTRACTED_STATIC_PATH = None
            _ATEXIT_REGISTERED = False

Theme System

Notes: - add_bootstrap() supports font_family and font_weights for Google Fonts injection. - set_component_defaults() modifies process-global defaults. Configure it at application startup before rendering components. - Component calls can pass None to clear a configured default for that instance. - convert_attrs() is available from faststrap directly for custom components and wrappers. - BaseComponent / Component are extension points for third-party class-based components; built-ins remain function-based. - BaseComponent.merge_attrs() applies convert_attrs() so class-based components keep the same attribute conversion behavior as function-based components.

When To Use BaseComponent

Use BaseComponent only when a class-based API is genuinely helpful, for example:

  • stateful third-party component libraries
  • reusable abstractions with internal helper methods
  • advanced wrappers that benefit from inheritance

For normal FastStrap components, prefer plain function-based components such as Button(...), Card(...), or Row(...). That remains the default style across the framework.

faststrap.core.base.Component

Bases: Protocol

Protocol for FastStrap components.

Source code in src/faststrap/core/base.py
class Component(Protocol):
    """Protocol for FastStrap components."""

    def render(self) -> Any:
        """Render component to FastHTML object."""
        ...

render()

Render component to FastHTML object.

Source code in src/faststrap/core/base.py
def render(self) -> Any:
    """Render component to FastHTML object."""
    ...

faststrap.core.base.BaseComponent

Bases: Component, ABC

Base class for stateful components with shared functionality.

Note: Most Faststrap components are implemented as functions for simplicity. This base class is provided for:

  1. Advanced users who want to create stateful component classes
  2. Future framework extensions that may need class-based components
  3. Third-party component libraries built on Faststrap

For most use cases, prefer function-based components as shown in the Faststrap component library (see Button, Card, Navbar, etc.).

Examples:

Creating a custom stateful component:

>>> from faststrap.core.base import BaseComponent
>>> from faststrap import Card, Button
>>>
>>> class StatefulCounter(BaseComponent):
...     def __init__(self, initial=0, **kwargs):
...         super().__init__(**kwargs)
...         self.count = initial
...
...     def render(self):
...         return Card(
...             Button(f"Count: {self.count}"),
...             **self.merge_attrs(cls="counter-card")
...         )
Source code in src/faststrap/core/base.py
class BaseComponent(Component, ABC):
    """Base class for stateful components with shared functionality.

    Note: Most Faststrap components are implemented as functions for simplicity.
    This base class is provided for:

    1. **Advanced users** who want to create stateful component classes
    2. **Future framework extensions** that may need class-based components
    3. **Third-party component libraries** built on Faststrap

    For most use cases, prefer function-based components as shown in the
    Faststrap component library (see Button, Card, Navbar, etc.).

    Examples:
        Creating a custom stateful component:

        >>> from faststrap.core.base import BaseComponent
        >>> from faststrap import Card, Button
        >>>
        >>> class StatefulCounter(BaseComponent):
        ...     def __init__(self, initial=0, **kwargs):
        ...         super().__init__(**kwargs)
        ...         self.count = initial
        ...
        ...     def render(self):
        ...         return Card(
        ...             Button(f"Count: {self.count}"),
        ...             **self.merge_attrs(cls="counter-card")
        ...         )
    """

    def __init__(self, *children: Any, **kwargs: Any):
        self.children = list(children)
        self.attrs = kwargs.copy()
        self._classes: list[str] = []

    @abstractmethod
    def render(self) -> Any:
        """Render component. Must be implemented by subclasses."""
        pass

    def add_class(self, *classes: str) -> "BaseComponent":
        """Add CSS classes fluently."""
        self._classes.extend(classes)
        return self

    def merge_attrs(self, **defaults: Any) -> dict[str, Any]:
        """Merge component attributes with defaults.

        The merged attribute set is passed through ``convert_attrs()`` so
        class-based components preserve the same HTMX, ``data_*``, ``aria_*``,
        ``style={...}``, and ``css_vars={...}`` behavior as function-based
        FastStrap components.
        """
        merged = {**defaults, **self.attrs}

        default_cls = defaults.get("cls")
        user_cls = self.attrs.get("cls")
        merged.pop("cls", None)

        attrs = convert_attrs(merged)

        all_classes = merge_classes(default_cls, user_cls, self._classes)
        if all_classes:
            attrs["cls"] = all_classes

        return attrs

add_class(*classes)

Add CSS classes fluently.

Source code in src/faststrap/core/base.py
def add_class(self, *classes: str) -> "BaseComponent":
    """Add CSS classes fluently."""
    self._classes.extend(classes)
    return self

merge_attrs(**defaults)

Merge component attributes with defaults.

The merged attribute set is passed through convert_attrs() so class-based components preserve the same HTMX, data_*, aria_*, style={...}, and css_vars={...} behavior as function-based FastStrap components.

Source code in src/faststrap/core/base.py
def merge_attrs(self, **defaults: Any) -> dict[str, Any]:
    """Merge component attributes with defaults.

    The merged attribute set is passed through ``convert_attrs()`` so
    class-based components preserve the same HTMX, ``data_*``, ``aria_*``,
    ``style={...}``, and ``css_vars={...}`` behavior as function-based
    FastStrap components.
    """
    merged = {**defaults, **self.attrs}

    default_cls = defaults.get("cls")
    user_cls = self.attrs.get("cls")
    merged.pop("cls", None)

    attrs = convert_attrs(merged)

    all_classes = merge_classes(default_cls, user_cls, self._classes)
    if all_classes:
        attrs["cls"] = all_classes

    return attrs

render() abstractmethod

Render component. Must be implemented by subclasses.

Source code in src/faststrap/core/base.py
@abstractmethod
def render(self) -> Any:
    """Render component. Must be implemented by subclasses."""
    pass

faststrap.core.theme.create_theme(primary=None, secondary=None, success=None, danger=None, warning=None, info=None, **extra_vars)

Create a custom theme from color values.

Parameters:

Name Type Description Default
primary str | None

Primary color (e.g., "#7BA05B")

None
secondary str | None

Secondary color

None
success str | None

Success color (defaults to Bootstrap green)

None
danger str | None

Danger color (defaults to Bootstrap red)

None
warning str | None

Warning color (defaults to Bootstrap yellow)

None
info str | None

Info color (defaults to Bootstrap cyan)

None
**extra_vars str

Additional CSS variables. Keys like surface_bg_dark, surface_shadow, or radius_lg are mapped to Faststrap design tokens (--fs-*); other keys map to Bootstrap variables (--bs-*).

{}

Returns:

Type Description
Theme

Theme instance

Examples:

>>> theme = create_theme(
...     primary="#7BA05B",
...     secondary="#48C774",
... )
>>> add_bootstrap(app, theme=theme, mode="dark")
Source code in src/faststrap/core/theme.py
def create_theme(
    primary: str | None = None,
    secondary: str | None = None,
    success: str | None = None,
    danger: str | None = None,
    warning: str | None = None,
    info: str | None = None,
    **extra_vars: str,
) -> Theme:
    """Create a custom theme from color values.

    Args:
        primary: Primary color (e.g., "#7BA05B")
        secondary: Secondary color
        success: Success color (defaults to Bootstrap green)
        danger: Danger color (defaults to Bootstrap red)
        warning: Warning color (defaults to Bootstrap yellow)
        info: Info color (defaults to Bootstrap cyan)
        **extra_vars: Additional CSS variables. Keys like ``surface_bg_dark``,
            ``surface_shadow``, or ``radius_lg`` are mapped to Faststrap design
            tokens (``--fs-*``); other keys map to Bootstrap variables
            (``--bs-*``).

    Returns:
        Theme instance

    Examples:
        >>> theme = create_theme(
        ...     primary="#7BA05B",
        ...     secondary="#48C774",
        ... )
        >>> add_bootstrap(app, theme=theme, mode="dark")
    """
    variables: dict[str, str] = {}

    # Add provided colors with auto-generated RGB values
    color_map = {
        "primary": primary,
        "secondary": secondary,
        "success": success,
        "danger": danger,
        "warning": warning,
        "info": info,
    }

    for name, color in color_map.items():
        if color:
            variables[f"--bs-{name}"] = color
            # Auto-generate RGB value from hex
            rgb = _hex_to_rgb(color)
            if rgb:
                variables[f"--bs-{name}-rgb"] = rgb

    # Add any extra variables
    for key, value in extra_vars.items():
        # Normalize key to CSS variable format
        if not key.startswith("--"):
            if key.startswith("fs_"):
                key = f"--fs-{key.removeprefix('fs_').replace('_', '-')}"
            elif key in _FASTSTRAP_THEME_TOKEN_NAMES:
                key = f"--fs-{key.replace('_', '-')}"
            else:
                key = f"--bs-{key.replace('_', '-')}"
        variables[key] = value

    return Theme(variables)

faststrap.core.theme.get_builtin_theme(name)

Get a built-in theme by name.

Parameters:

Name Type Description Default
name str

Theme name (e.g., "green-nature", "blue-ocean")

required

Returns:

Type Description
Theme

Theme instance

Raises:

Type Description
ValueError

If theme name is not found

Source code in src/faststrap/core/theme.py
def get_builtin_theme(name: str) -> Theme:
    """Get a built-in theme by name.

    Args:
        name: Theme name (e.g., "green-nature", "blue-ocean")

    Returns:
        Theme instance

    Raises:
        ValueError: If theme name is not found
    """
    if name not in _BUILTIN_THEMES:
        available = ", ".join(sorted(_BUILTIN_THEMES.keys()))
        raise ValueError(f"Unknown theme: '{name}'. Available themes: {available}")
    cached = _BUILTIN_THEME_CACHE.get(name)
    if cached is not None:
        return cached
    theme = Theme(_BUILTIN_THEMES[name])
    _BUILTIN_THEME_CACHE[name] = theme
    return theme

faststrap.core.theme.list_builtin_themes()

List all available built-in theme names.

Returns:

Type Description
list[str]

List of theme names

Source code in src/faststrap/core/theme.py
def list_builtin_themes() -> list[str]:
    """List all available built-in theme names.

    Returns:
        List of theme names
    """
    return list(_BUILTIN_THEMES.keys())

faststrap.core.theme.set_component_defaults(component, **defaults)

Set default values for a component globally.

This updates process-global state shared by all requests. Configure defaults during application startup.

Parameters:

Name Type Description Default
component str

Component name (e.g., "Button")

required
**defaults Any

Default values to set

{}

Examples:

>>> set_component_defaults("Button", variant="outline-primary", size="sm")
>>> # Now all Button() calls use these defaults unless overridden
Source code in src/faststrap/core/theme.py
def set_component_defaults(component: str, **defaults: Any) -> None:
    """Set default values for a component globally.

    This updates process-global state shared by all requests.
    Configure defaults during application startup.

    Args:
        component: Component name (e.g., "Button")
        **defaults: Default values to set

    Examples:
        >>> set_component_defaults("Button", variant="outline-primary", size="sm")
        >>> # Now all Button() calls use these defaults unless overridden
    """
    global _COMPONENT_DEFAULTS_WARNED

    with _COMPONENT_DEFAULTS_LOCK:
        if _COMPONENT_DEFAULTS_LOCKED and not _COMPONENT_DEFAULTS_WARNED:
            warnings.warn(
                "set_component_defaults() updates process-global state. "
                "Prefer calling it during application startup before handling requests.",
                RuntimeWarning,
                stacklevel=2,
            )
            _COMPONENT_DEFAULTS_WARNED = True

        if component not in _COMPONENT_DEFAULTS:
            _COMPONENT_DEFAULTS[component] = {}
        _COMPONENT_DEFAULTS[component].update(defaults)

faststrap.core.theme.reset_component_defaults(component=None)

Reset component defaults to original values.

Parameters:

Name Type Description Default
component str | None

Component name to reset, or None to reset all

None
Source code in src/faststrap/core/theme.py
def reset_component_defaults(component: str | None = None) -> None:
    """Reset component defaults to original values.

    Args:
        component: Component name to reset, or None to reset all
    """
    global _COMPONENT_DEFAULTS, _COMPONENT_DEFAULTS_LOCKED, _COMPONENT_DEFAULTS_WARNED

    with _COMPONENT_DEFAULTS_LOCK:
        if component is None:
            # Reset all components to original defaults
            _COMPONENT_DEFAULTS = {k: v.copy() for k, v in _DEFAULT_COMPONENT_DEFAULTS.items()}
            _COMPONENT_DEFAULTS_LOCKED = False
            _COMPONENT_DEFAULTS_WARNED = False
            _BUILTIN_THEME_CACHE.clear()
        elif component in _DEFAULT_COMPONENT_DEFAULTS:
            # Reset specific component to original default
            _COMPONENT_DEFAULTS[component] = _DEFAULT_COMPONENT_DEFAULTS[component].copy()

Component Discovery

Faststrap exposes a small registry so users and agents can discover existing components before inventing new wrappers.

from faststrap import (
    find_components,
    get_component,
    get_components_by_pattern,
    list_component_metadata,
    list_components,
)

list_components(category="display")
find_components("card")
get_components_by_pattern("toast")
metadata = list_component_metadata()
Button = get_component("Button")

faststrap.core.registry.list_components(category=None)

List all registered components, optionally filtered by category. Args: category: Filter by category (layout, display, feedback, etc.) Returns: List of component names Examples: >>> list_components(category="feedback") ['Alert', 'Toast', 'Modal', 'Spinner']

Source code in src/faststrap/core/registry.py
def list_components(category: str | None = None) -> list[str]:
    """List all registered components, optionally filtered by category.
    Args:
        category: Filter by category (layout, display, feedback, etc.)
    Returns:
        List of component names
    Examples:
        >>> list_components(category="feedback")
        ['Alert', 'Toast', 'Modal', 'Spinner']
    """
    ensure_autodiscovered()

    if category is None:
        return list(_component_registry.keys())

    return [name for name, meta in _component_registry.items() if meta.get("category") == category]

faststrap.core.registry.find_components(query, *, category=None)

Find components by name, category, module, or docstring text.

Source code in src/faststrap/core/registry.py
def find_components(
    query: str,
    *,
    category: str | None = None,
) -> list[str]:
    """Find components by name, category, module, or docstring text."""
    ensure_autodiscovered()
    normalized = query.casefold().strip()
    if not normalized:
        return list_components(category=category)

    matches = []
    for name, meta in _component_registry.items():
        if category is not None and meta.get("category") != category:
            continue
        haystack = " ".join(
            [
                name,
                str(meta.get("category") or ""),
                str(meta.get("module") or ""),
                str(meta.get("doc") or ""),
            ]
        ).casefold()
        if normalized in haystack:
            matches.append(name)
    return matches

faststrap.core.registry.get_components_by_pattern(pattern, *, category=None)

Return component callables matching a pattern.

This is a convenience API for agents and app builders that need to discover existing components before inventing new UI.

Source code in src/faststrap/core/registry.py
def get_components_by_pattern(
    pattern: str, *, category: str | None = None
) -> list[Callable[..., Any]]:
    """Return component callables matching a pattern.

    This is a convenience API for agents and app builders that need to discover
    existing components before inventing new UI.
    """
    return [
        component
        for name in find_components(pattern, category=category)
        if (component := get_component(name)) is not None
    ]

faststrap.core.registry.list_component_metadata(category=None)

List registered component metadata records.

Parameters:

Name Type Description Default
category str | None

Optional category filter.

None

Returns:

Type Description
list[dict[str, Any]]

Metadata dictionaries with a stable name key added.

Source code in src/faststrap/core/registry.py
def list_component_metadata(category: str | None = None) -> list[dict[str, Any]]:
    """List registered component metadata records.

    Args:
        category: Optional category filter.

    Returns:
        Metadata dictionaries with a stable ``name`` key added.
    """
    ensure_autodiscovered()
    records = []
    for name, meta in _component_registry.items():
        if category is not None and meta.get("category") != category:
            continue
        records.append({"name": name, **meta})
    return records

faststrap.core.registry.get_component(name)

Get component function by name.

Source code in src/faststrap/core/registry.py
def get_component(name: str) -> Callable[..., Any] | None:
    """Get component function by name."""
    ensure_autodiscovered()
    return _component_registry.get(name, {}).get("func")

Theme Variant CSS

Use theme_variant_css() when a polished component needs small light/dark CSS differences without repeating selector boilerplate.

from fasthtml.common import Style
from faststrap import theme_variant_css

Style(
    theme_variant_css(
        ".premium-card",
        light={"background": "rgba(255, 255, 255, 0.78)"},
        dark={"background": "rgba(15, 23, 42, 0.62)"},
    )
)

faststrap.core.theme.theme_variant_css(selector, *, light=None, dark=None)

Create light/dark CSS blocks for a selector.

This helper keeps premium component styling aligned with Faststrap's data-bs-theme mode convention without requiring every app to hand-write repetitive selectors.

Examples:

>>> theme_variant_css(
...     ".metric-card",
...     light={"background": "rgba(255,255,255,.85)"},
...     dark={"background": "rgba(15,23,42,.72)"},
... )
Source code in src/faststrap/core/theme.py
def theme_variant_css(
    selector: str,
    *,
    light: dict[str, str] | None = None,
    dark: dict[str, str] | None = None,
) -> Style:
    """Create light/dark CSS blocks for a selector.

    This helper keeps premium component styling aligned with Faststrap's
    ``data-bs-theme`` mode convention without requiring every app to hand-write
    repetitive selectors.

    Examples:
        >>> theme_variant_css(
        ...     ".metric-card",
        ...     light={"background": "rgba(255,255,255,.85)"},
        ...     dark={"background": "rgba(15,23,42,.72)"},
        ... )
    """
    blocks = []
    if light:
        blocks.append(f'[data-bs-theme="light"] {selector} {{\n{_format_declarations(light)}\n}}')
    if dark:
        blocks.append(f'[data-bs-theme="dark"] {selector} {{\n{_format_declarations(dark)}\n}}')
    return Style("\n".join(blocks))