# Fragments
URL: /chirp/docs/build-apps/html-fragments/fragments/
Section: html-fragments
Tags: fragments, htmx, blocks, page, oob
--------------------------------------------------------------------------------
The Key Innovation Most Python frameworks treat templates as "render a full page, return a string." Chirp can render a named block from a template independently, without rendering the rest of the page. This is what makes htmx integration seamless. The browser requests a fragment, the server returns just the block it needs. Fragment Fragment renders a specific block from a template: from chirp import Fragment @app.route("/search") def search(request: Request): results = do_search(request.query.get("q", "")) if request.is_fragment: return Fragment("search.html", "results_list", results=results) return Template("search.html", results=results) Arguments: File Template path Path to the template file (relative to template_dir). Block name The named block to render. Must exist in the template. Database Keyword arguments Become the rendering context passed to the template. The template: {% extends "base.html" %} {% block content %} <input type="search" hx-get="/search" hx-target="#results" name="q"> {% block results_list %} <div id="results"> {% for item in results %} <div class="result">{{ item.title }}</div> {% endfor %} </div> {% endblock %} {% endblock %} Full page request renders everything (base layout + content + results). Fragment request renders only results_list -- the <div id="results"> and its contents. request.is_fragment The Request object detects htmx requests automatically: request.is_fragment # True if HX-Request header present request.htmx_target # Value of HX-Target header (e.g., "#results") request.htmx_trigger # Value of HX-Trigger header request.is_history_restore # True if htmx history restore Page Page is syntactic sugar that auto-detects whether to return a full page or a fragment: from chirp import Page @app.route("/search") def search(request: Request): results = do_search(request.query.get("q", "")) return Page("search.html", "results_list", results=results) If request.is_fragment is True, it renders the block. Otherwise, it renders the full template. This eliminates the if/else pattern. When a route needs two fragment scopes, pass page_block_name: return Page( "search.html", "results_list", page_block_name="page_root", results=results, ) results_list stays the narrow fragment target for explicit swaps page_root becomes the fragment-safe root for boosted navigation OOB (Out-of-Band Swaps) Sometimes a single action needs to update multiple parts of the page. OOB sends a primary fragment plus additional out-of-band fragments in one response: from chirp import OOB, Fragment @app.route("/cart/add", methods=["POST"]) async def add_to_cart(request: Request): item = await add_item(request) return OOB( Fragment("cart.html", "cart_items", items=get_cart()), Fragment("layout.html", "cart_count", count=cart_count()), Fragment("layout.html", "total_price", total=cart_total()), ) The first fragment is the main response. Additional fragments are appended with hx-swap-oob="true", so htmx swaps them into the correct locations on the page. ValidationError A specialized fragment for form validation errors. Returns a 422 status: from chirp import ValidationError @app.route("/register", methods=["POST"]) async def register(request: Request): form = await request.form() errors = validate_registration(form) if errors: return ValidationError("register.html", "form_errors", errors=errors) # ... create user This renders the form_errors block with a 422 status code, which htmx can handle with hx-target-422 or a custom error handler. Block Availability render_block() resolves inherited blocks. You can render a parent-defined page root from a child template, and child overrides still win inside that parent block. {# child.html #} {% extends "base.html" %} {% block search_results %} <div id="results">...</div> {% endblock %} {# base.html #} {% block page_root %} <section class="page-shell"> {% block search_results %}{% endblock %} </section> {% endblock %} In this shape: render_block("search_results") returns only the inner results fragment render_block("page_root") returns the full page shell with the child's search_results block injected How Chirp Finds Blocks Chirp uses Kida's template_metadata() to introspect templates at build time. Block names, regions, and dependencies come from the AST — Chirp never hard-codes which blocks exist. That enables: Validation — fragment_block and page_block are checked before render OOB discovery — Blocks named *_oob are discovered automatically for app shells Layout contracts — depends_on and cache_scope from each block drive when OOB regions are rendered See Kida Integration for the full flow. Regions for Shell OOB {% region %} is the preferred pattern for app shell updates (breadcrumbs, sidebar, title). One definition serves both full-page slots and OOB swaps — no duplication. Minimal layout example: {% region breadcrumbs_oob(breadcrumb_items=[{"label":"Home","href":"/"}]) %} {{ breadcrumbs(breadcrumb_items) }} {% end %} {% region title_oob(page_title="My App") %} <title id="chirpui-document-title" hx-swap-oob="true">{{ page_title }}</title> {% end %} {% region sidebar_oob(current_path="/") %} {{ sidebar(current_path=current_path) }} {% end %} {% call app_shell(brand="My App") %} {% slot topbar %} {{ breadcrumbs_oob(breadcrumb_items=breadcrumb_items | default([{"label":"Home","href":"/"}])) }} {% end %} {% slot sidebar %} {{ sidebar_oob(current_path=current_path | default("/")) }} {% end %} {% block content %}{% end %} {% end %} ChirpUI's breadcrumbs_oob, sidebar_oob, and title_oob map to chirpui-topbar-breadcrumbs, chirpui-sidebar-nav, and chirpui-document-title automatically. See Kida Integration for the full flow and examples/chirpui/shell_oob for a complete reference. Block-Heavy Layouts Templates with many blocks (extends, nested blocks, fragments) benefit from a clear structure. Use the extension block pattern so child templates can add content without replacing parent layout. Boost Layout Pattern Extend chirp/layouts/boost.html for htmx-boost + SSE apps. The layout defines stable blocks: Block Purpose title Page title head Extra head content (styles, meta) head_style Inline CSS (e.g. view-transition overrides) body_before Content before #main content Main content (inside #main, swapped on navigation) sse_scope SSE connection (outside #main so it persists) body_after Scripts (event delegation, theme toggle) content is swapped on navigation; sse_scope and body_after stay in place. Put event delegation and other scripts in body_after so they run once and work for dynamically swapped content. Extension Blocks Since Kida doesn't support super(), use explicit extension blocks in your base template: {% block head %} <link rel="stylesheet" href="/css/base.css"> {% block extra_head %}{% end %} {% end %} {% block body_after %} <script src="/js/main.js"></script> {% block extra_scripts %}{% end %} {% end %} Child templates override extra_head or extra_scripts to add content without replacing the base. Nesting for Fragments Define fragment blocks inside the block that gets swapped. For example, if page_root is the page-level swap target, put results_list inside it so the fragment target (#results) exists in the DOM. Next Steps Return Values -- All return types Server-Sent Events -- Push fragments in real-time htmx Patterns -- Common fragment patterns
--------------------------------------------------------------------------------
Metadata:
- Word Count: 958
- Reading Time: 5 minutes