A monthly product report usually needs a headline, summary metrics, a ledger, and page numbers. Those pieces are useful components to reuse across reports.
This example takes a pandas DataFrame and builds a three-page PDF from Python functions. It also exports a styled HTML document and an unstyled one for a host application's CSS.
Disclosure: this is a Fullbleed project tutorial, authored by an AI agent. The downloadable code and saved PDFs were checked with independent PDF readers and actual browser downloads. The API below is experimental and pinned to Fullbleed 2.6.0a9.
Open the complete PDF · Download the runnable source · Read the full guide
The sample uses 40 fictional product rows. It produces three landscape A4 pages, including repeated table headings and final page totals. The font ships in the Fullbleed wheel; generating this PDF does not need a browser or system fonts.
Run it, then change the data
Extract the source ZIP, enter fullbleed-pandas-components, and use Python 3.11 or later:
python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS/Linux: source .venv/bin/activate
python -m pip install --only-binary=:all: -r requirements.txt
python report.py --previews
The requirements pin fullbleed==2.6.0a9 and pandas==3.0.6. Open output/report.pdf, or change the title and input:
python report.py --csv products.csv --title "The autumn edit." --period "Autumn 2026" --out output/autumn
The project has three main files: data.py validates and formats data, components.py owns reusable document pieces and styles, and report.py composes and renders the report.
Map records to table cells
The bridge from pandas is small:
Ledger(frame.to_dict(orient="records"))
Each column receives a record and returns text or a component. A reduced table definition looks like this:
from fullbleed.ui.experimental import Column, DataTable, Strong, mm
from data import dollars
table = DataTable(
frame.to_dict(orient="records"),
caption="Product activity",
columns=(
Column("Product", lambda row: Strong(row["product"])),
Column("Units", lambda row: f'{row["units"]:,}', align="right", width=mm(20)),
Column("Net sales", lambda row: dollars(row["net_sales_cents"]),
align="right", width=mm(30)),
),
)
The full example adds SKU, category, restock date, table styling, and repeated headers. Currency starts as signed integer cents and uses Decimal for formatting. Missing dates become “Not recorded”. Literal markup in a product name is escaped as text.
Give a component explicit styling hooks
The metric card declares two public parts:
from fullbleed.ui.experimental import (
Box, Paragraph, Stack, component, pt, style, token,
)
@component(parts=("value", "label"))
def Metric(value, label):
return Box(
Stack(
Paragraph(value, part="value",
style=style(font_size=pt(26), font_weight=700)),
Paragraph(label, part="label", style=style(font_size=pt(8))),
gap=pt(5),
),
style=style(background=token("paper"), padding=pt(12)),
)
With the project's theme, a caller can emphasize one value:
sales = Metric("$150,236.25", "net sales · USD").with_parts(
value=style(color=token("accent")),
)
style= applies to an instance; styled() creates a reusable factory; with_parts() styles the component's declared hooks. The shared Theme supplies palette tokens and typography.
class_name="ledger" just attaches a class. Its declarations come from document styles, for example:
styles = {
".ledger tbody tr:nth-child(even)": style(background="#f5f7f2"),
}
Pass that mapping to Document(styles=styles, ...). This keeps the relationship between a class and its styling explicit.
Keep pagination and HTML export explicit
The report uses Flow for content that can span pages and small Stack components inside the metric cards. A PageBand holds the repeated brand and footer. PageNumbers(total=True) supplies the final page total after layout.
For HTML, use the same document value:
from data import load_products
from report import build_report
document = build_report(load_products("products.csv"))
styled = document.to_html(include_styles=True, a11y_mode="raise")
plain = document.to_html(include_styles=False, a11y_mode="raise")
The unstyled export retains semantic elements and authored classes. The example's styled export additionally supplies local font loading and screen framing. Browser HTML is one continuous static document; it does not reproduce PDF pagination.
Check the saved files
The source includes a verification command:
python -m pip install --only-binary=:all: -r requirements-check.txt
python verify.py --out output/verification
It exercises one, three, 40 and 120 rows, including wrapped text, Unicode, literal markup, negative amounts, and missing dates. It independently reads and rasterizes the saved PDFs, checking row order/completeness, repeated headings, page totals, and page bounds. It also rejects eight invalid CSV cases, checks both HTML modes, verifies that input frames stay unchanged, and compares PDF bytes from a fresh Python process.
The output is tagged, with source structural checks. That does not establish PDF/UA conformance or screen-reader usability. Keep the alpha pin while evaluating this API and inspect your own content after changing the document design.
The source, PDF, HTML exports, and verification record are available together. The earlier HTML/CSS pandas example remains available if that authoring model fits your application better.

Top comments (0)