Skip to content

Styling and colors

TextStyle is an immutable record where every property is optional:

public sealed record TextStyle(
float? FontSize = null,
FontWeight? FontWeight = null, // LIGHT … BLACK on the CSS scale
bool? Italic = null,
bool? Underline = null,
Color? Color = null,
TextAlignment? Alignment = null, // LEFT, CENTER, RIGHT, JUSTIFY
string? FontFamily = null,
bool? Strikethrough = null,
TextScript? Script = null, // Superscript / Subscript
float? LetterSpacing = null); // points per glyph, negative tightens

Font weights beyond Normal/Bold render as true cuts only in PDF with fonts registered per weight (doc.RegisterFontVariant("Brand", FontWeight.Light, lightTtf)); the built-in fonts and DOCX/XLSX collapse to regular/bold (SemiBold and up count as bold). Letter spacing has no XLSX equivalent and is dropped there; sub/superscript in XLSX styles the whole cell (a worksheet cell holds one font).

Every property also has a fluent With* method, so TextStyle.Default (an all-null style) can be chained instead of naming every property up front:

TextStyle.Default.WithFontSize(24f).Bold().WithColor(Colors.Blue.Medium)

Bold(), AlignLeft()/AlignCenter()/AlignRight()/AlignJustify(), and Superscript()/Subscript() are shortcuts over WithFontWeight, WithAlignment, and WithScript. Both forms produce the same immutable record - pick named-argument construction when building a style from data, and the fluent chain when composing one inline.

Unset properties inherit. A style you pass at a text run merges with the document’s DefaultTextStyle; whatever the run doesn’t specify falls back:

doc.DefaultTextStyle(TextStyle.Default.WithFontSize(10f).WithFontFamily("Liberation Sans"))
.Paragraph(p => p.Text("10pt body text"))
.Paragraph(p => p.Text("24pt heading, still Liberation Sans",
TextStyle.Default.WithFontSize(24f)));

Define your document’s styles once as constants and reuse them - see your first invoice for the pattern.

A run-appending call like Text(...) also accepts styling chained directly off it (.Text("...").FontSize(14).Bold()), as an alternative to passing a TextStyle argument - see Styling a run inline. To apply a whole style built elsewhere, chain WithStyle(preset): its explicit properties win, and it falls back to whatever the run already had for anything it leaves unset - the same fallback direction as Merge above, just applied at the call site instead of at layout time.

Block-level layout options live on ParagraphStyle - same nullable-and-inherit pattern, with DefaultParagraphStyle as the document-wide fallback:

public sealed record ParagraphStyle(
float? SpacingBefore = null, // points above the paragraph
float? SpacingAfter = null, // points below
float? LineSpacing = null, // multiple of the font size; built-in 1.2
float? IndentLeft = null,
float? IndentRight = null,
float? IndentFirstLine = null,
Color? BackgroundColor = null);

Same fluent With* methods and a ParagraphStyle.Default anchor apply here too: ParagraphStyle.Default.WithSpacingAfter(12f).WithLineSpacing(1.5f). These options are also chainable directly off Paragraph(...) (.Paragraph(...).SpacingAfter(12f).LineSpacing(1.5f)) with a WithStyle(preset) for a whole reusable style - see Paragraph layout.

Usage and per-format behavior are covered in text and paragraphs.

Table cells take a CellStyle - built as a record or fluently from CellStyle.Default. Cell(...) also accepts the same styling chained directly off the call - see Cells.

var header = new CellStyle(
Padding: new Thickness(8f, 6f),
Border: new CellBorder(0.75f, Colors.Grey.Lighten1),
BackgroundColor: Colors.Grey.Lighten3,
HorizontalAlignment: TextAlignment.Left,
VerticalAlignment: VerticalAlignment.Middle);
var sameHeader = CellStyle.Default
.WithPadding(new Thickness(8f, 6f))
.WithBorder(new CellBorder(0.75f, Colors.Grey.Lighten1))
.WithBackgroundColor(Colors.Grey.Lighten3)
.WithHorizontalAlignment(TextAlignment.Left)
.WithVerticalAlignment(VerticalAlignment.Middle);

CellBorder takes either one uniform width or an independent width per side (left, top, right, bottom) with one shared color and one shared stroke style - a side with width 0 is not drawn:

new CellBorder(1f, Colors.Grey.Lighten1); // all four sides
new CellBorder(0f, 0f, 0f, 1f); // bottom only, explicit
CellBorder.BottomOnly(1f, Colors.Grey.Lighten1); // same, via factory
CellBorder.TopOnly(2f); // also: Horizontal, Vertical
new CellBorder(1f, style: LineStyle.Dashed); // also: LineStyle.Dotted

The style applies to all four sides, like the color. See the tables guide for the per-format mapping (XLSX’s single style axis makes non-solid styles lossy there).

Color parses hex in four forms and converts implicitly from strings:

new TextStyle(Color: "#0F766E"); // #RRGGBB
new TextStyle(Color: "#80FF0000"); // #AARRGGBB - 50% alpha red
new TextStyle(Color: "#F00"); // #RGB shorthand
TextStyle.Default.WithColor("#0F766E"); // fluent form - implicit string conversion still applies
someColor.WithAlpha(128); // derive a translucent variant

Alpha behaves differently per format: PDF renders true transparency, DOCX blends against white, XLSX stores the ARGB value. See the feature support matrix.

The full Google Material palette ships as constants - 19 hues, each with Lighten5–Lighten1, Medium, Darken1–Darken4, and Accent1–Accent4:

Colors.Blue.Medium
Colors.Grey.Lighten3
Colors.Teal.Darken2
Colors.Red.Accent2

A visual swatch grid is in page sizes and colors.