Components & reuse
The single most useful pattern for non-trivial documents: factor a repeated piece ( a header band, an address block, a signature line, a styled callout) into a reusable component so you write it once and use it everywhere.
If you’ve used QuestPDF, this is the answer to its IComponent. Rivoli PDF has no
special component interface, and it doesn’t need one: a component is just a method
(or a small class) that returns a Block you can drop into any section.
A component is a method that returns a Block
Section titled “A component is a method that returns a Block”The whole pattern in one example. A reusable “callout box” built on
ContainerBlock:
using Rivoli.Pdf.Model;using Rivoli.Pdf.Styling;
static Block Callout(string title, string body, Color accent){ return new ContainerBlock() .WithBackground(Color.FromRgb(245, 245, 245)) .WithBorder(accent, 1) .WithPadding(12) .Add(new Paragraph(title) { Style = new Style().Bold() }) .Add(new Paragraph(body));}Use it anywhere a block goes:
section.Add(Callout("Note", "Back up before upgrading.", Color.Blue));section.Add(Callout("Warning", "This action cannot be undone.", Color.Red));Because the component returns a Block, it composes exactly like a built-in block:
add it to a Section, nest it inside another ContainerBlock, or return it from
another component.
Parameterize what varies
Section titled “Parameterize what varies”A good component exposes the parts that change as parameters and bakes in the parts that don’t. An address block, for instance:
static Block AddressBlock(string name, string street, string cityLine){ return new ContainerBlock() .Add(new Paragraph(name) { Style = new Style().Bold() }) .Add(new Paragraph(street)) .Add(new Paragraph(cityLine));}
section.Add(AddressBlock("Acme Corp", "123 Main St", "Springfield, IL 62704"));Stateful components as classes
Section titled “Stateful components as classes”When a component needs configuration that’s reused across many calls (a brand
color, a logo path, default fonts), promote it to a class. The class holds the
shared state; a Render/Build method returns the Block.
public sealed class BrandedHeader{ private readonly string _company; private readonly Color _accent;
public BrandedHeader(string company, Color accent) { _company = company; _accent = accent; }
public Block Build(string pageTitle) { return new ContainerBlock() .Horizontal(spacing: 12) .Add(new Paragraph(_company) { Style = new Style().Bold().WithForegroundColor(_accent) }) .Add(new Paragraph(pageTitle)); }}
// usagevar header = new BrandedHeader("Acme Corp", Color.FromRgb(0, 90, 160));section.Add(header.Build("Quarterly Report"));section.Add(header.Build("Appendix")); // same brand, different titleThis is the closest analogue to a QuestPDF IComponent: an object that carries
state and produces document content on demand. The difference is that it’s an
ordinary class returning an ordinary Block: no interface to implement, nothing to
register.
Components that take child content
Section titled “Components that take child content”Sometimes a component is a wrapper: it provides framing and lets the caller supply the inside. Pass the children in, or accept a configuration delegate:
static ContainerBlock Card(string heading, Action<ContainerBlock> content){ var card = new ContainerBlock() .WithBackground(Color.White) .WithBorder(Color.FromRgb(220, 220, 220), 1) .WithCornerRadius(4) .WithPadding(16) .Add(new Paragraph(heading) { Style = new Style().Bold().WithFontSize(14) });
content(card); // caller fills the body return card;}
// usagesection.Add(Card("Summary", body =>{ body.Add(new Paragraph("Revenue grew 18% year over year.")); body.Add(List.Unordered().Add("New markets").Add("Higher retention"));}));Guidelines
Section titled “Guidelines”- Return a
Block(often aContainerBlock) so components compose like first-class content. - Keep them pure where you can: input parameters in, a fresh block out, no shared mutable state. Pure components are trivial to test and reuse.
- Promote to a class only when there’s genuine shared configuration.
- Build a small library of components for your team: a
Componentsstatic class is a fine home.
See it in context in the examples gallery, and read Document structure for assembling components into whole documents.