Taipy App — UI Architecture
The Taipy dashboard uses a template-driven architecture where pages are declarative data, not imperative layout code. All page rendering flows through page_template.py → build_page(cfg: PageConfig). This document defines the rules for maintaining and extending it.
Adding a New Page
A new page requires exactly 3 files and 2 edits (4 files for dashboard pages):
hf_taipy_app/src/state/<page_name>.py— State variables, callbacks, SQL queries, chart rendering. Must follow prefix naming (<prefix>_variable) to avoid Taipy namespace collisions.hf_taipy_app/src/pages/<page_name>.py— Apage_config: PageConfigandpage_md: str(frombuild_page(page_config)). No hand-crafted Taipy Markdown — the page file is pure configuration.hf_taipy_app/src/main.py— Import the page'spage_configandpage_md, add aPageEntrytoPAGE_REGISTRY.hf_taipy_app/src/template.py— Add page-specific glossary terms toPAGE_TERMS.
Dashboard Page Variant
For operations/dashboard pages (stats cards + full-width content instead of 3fr/1fr layout):
- Use
stats: list[StatCard]inPageConfiginstead ofmetrics— this triggers the dashboard layout (_build_dashboard_page), which wraps content in a viewport-contained scroll wrapper (ll-dashboard-scroll). - Call
register_page_refresher("Page-Name", refresh_fn, is_dashboard=True)— theis_dashboardflag ensures the site-wide footer is hidden (dashboard pages render the footer inside the scroll wrapper). ContentRowwraps content blocks.StatCarddefines the stat cards in the top bar.
Template Rules
- All pages must use
build_page(): Zero hand-crafted layouts. A page is aPageConfig(title, icon, description, metrics, sidebar widgets, content blocks, citations), not a string of Taipy Markdown. Metricrequireshelp_text: If the metric name is not universally understood,help_textis mandatory — thePageConfigdataclass enforces this. "What does this mean?" and "Is this good or bad?" must be answerable from the tooltip alone.SidebarWidgetrequireshelp: Every filter widget must have ahelptooltip explaining what it controls. Help icons are positioned absolute-right of the widget via CSS (.md-para:has(> .ll-help)), keeping all widget widths identical regardless of help presence.Citationfor every methodology: Any page implementing a published algorithm must include aCitation(text, url)in itsPageConfig. No uncited methodologies. Practitioner methodologies (course materials, coaching frameworks) useCitation(text)without a URL but must include "(course materials)" in the label.NOTICEfile maintenance: When adding a new analytics module, page, or algorithm, add a corresponding entry to theNOTICEfile in the project root. The NOTICE file is the authoritative record of all third-party data attributions, library credits, and mathematical/academic references. EveryCitationin aPageConfigand everyreferences:entry in a workflow card must have a corresponding NOTICE entry. Update NOTICE in the same change — not as a follow-up.StatCardfor dashboard stat cards: Dashboard pages usestats: list[StatCard]inPageConfig. Each card haslabel,var, optionaldetail_var,help_text, anddetail_html. The presence ofstatsactivates the dashboard layout branch. Setdetail_html=Trueto renderdetail_varas raw HTML via a content provider iframe (supports inline<span style="">coloring); defaultFalserenders as plain text. Convention: everyStatCardshould havehelp_text(same rationale asMetric).RequiredFilterfor filter requirements: Pages that cannot display data until specific sidebar filters are set must declarerequired_filters: list[RequiredFilter]inPageConfig. The template auto-generatesempty_condition+empty_messagefrom these when not explicitly provided. This replaces hand-crafted filter-null-check conditions and ensures the "Select a X and Y to begin" guidance appears consistently. When a filter is required on some pages but optional on others (e.g., Team),template.pydefines separateSidebarWidgetinstances with differentconditiontuples — one withrequired=True(no label suffix) and one withrequired=False(appends "(optional)"). SubView pages handle empty states per-SubView and do not use page-levelrequired_filters.ContentBlockfor all content: Images useContentBlock("image", var), tables useContentBlock("table", var), Plotly charts useContentBlock("chart", var). Tables accepttable_cell_class_name={column: callback_name}for per-cell CSS styling via Taipy'scell_class_nameattribute (the callback returns a CSS class string). Never construct raw<|{var}|chart|>markup in page files.- WCAG color-independence on table columns: Table columns that use color for categorization (e.g., Type, Freshness) must include a
::beforeshape marker as a WCAG 1.4.1 secondary visual cue. Each category gets a distinct CSS-drawn shape (circle, diamond, triangle, square, ring) viacurrentColorso shapes inherit the text color. If the page includes a legend (e.g., DAG legend), shapes and colored text in the legend must match the table column markers. - Layout changes go through the template: If a visual change requires editing more than one page file, it belongs in
page_template.py. Individual page files contain only page-specific data. _FOOTER_CONTENTfor footer text: The footer text ("Published Datasets") is a shared constant inpage_template.py. Dashboard pages render it inside the scroll wrapper; other pages render it as the site-wide footer. Do not hardcode footer text in page files.is_dashboard=Trueonregister_page_refresher: Required for dashboard pages. Controlsshow_site_footerstate variable — omitting it causes footer duplication.ll-dashboard-scrollfor dashboard viewport: Dashboard content is wrapped in a viewport-contained scroll area (overflow: auto,max-height: calc(100vh - 245px)). Both horizontal and vertical scrollbars live inside this container. The horizontal scrollbar stays at the viewport bottom.- State module isolation: Each page's state module manages its own variables and callbacks. Shared state (competition/team/match filters) lives in
state/shared.py. No cross-page state imports except fromshared. - Never use
tp_as a state variable prefix: Taipy reservestp_internally for its expression evaluator (TpExPr_). Variables starting withtp_will have state updates silently dropped — no error, no warning. Also avoidtpec_(Taipy edge-case prefix). Safe prefixes:tac_,gk_,ts_,pt_,dv_, etc. - Glossary coverage: Every domain-specific term used in metric names, chart labels, or descriptions must have an entry in
GLOSSARY(intemplate.py) and be listed in the page'sPAGE_TERMSentry. - Server-driven autocomplete (
SidebarWidget.kind="combobox"): Dropdowns whose LOV would exceed ~200 items must use thecomboboxkind withsearch_var+on_search_changeinstead of the client-sidefilterable=Trueflag. The template emits a<|{var}|ll_ext.combobox|...|>fragment backed by the in-repo Taipy GUI extension atsrc/extensions/ll_ext/(React + MUI Autocomplete, bundle built intofront-end/dist/library.js). Behaviour is WAI-ARIA APG combobox-with-list-autocomplete: the listbox is hidden until the user types, opens via ArrowDown / typing, closes on Escape / selection / blur, and full keyboard navigation +aria-activedescendanthighlighting is handled natively. The search-change callback hits afilters.search_*function (SQL-backed) or filters an in-memory map (for DV / TAC where the candidate set is already loaded for the page's primary use). Every callback must set the LOV to[..., filters.NO_MATCHES_SENTINEL]when the match list is empty — Taipy's state diff treatsstate.lov = []as a no-op, so the bare empty list leaves a stale LOV visible. Previous provisional pattern (searchable=Trueflag onkind="dropdown") was removed 2026-04-18 once all 8 migrations landed; the flag no longer exists onSidebarWidget.
Why Template-First
Building pages as imperative layout code leads to inconsistency debt that compounds per-page. With 12+ pages, hand-crafting each one guarantees: missing tooltips on some pages, different metric formats, inconsistent empty-state handling, and layout drift. The template makes these structurally impossible — required fields are constructor parameters, not afterthoughts. A CHI audit against a template architecture produces template-level fixes (one change, all pages); without it, the same audit produces N per-page fixes.