Themes & Styles

GptMarkdown v1.2.1 has three customization layers. They never overlap — each serves a distinct purpose.

Style object

Appearance: colours, sizes, padding, fonts

GptMarkdownStyleSheet

Builder

Structure: replace the widget entirely

codeBuilder, blockQuoteBuilder, …

Theme data

Defaults for the whole app or a subtree

GptMarkdownThemeData

The rule: style object for looks, builder for structure. A builder loses every future improvement to that component; a style object keeps it.

Layer 1 — GptMarkdownStyleSheet

Pass a GptMarkdownStyleSheet to a single widget. Every field is optional; anything unset keeps the package default — adding a style sheet never changes how existing content looks.

widget_style.dart
1// Per-widget: only this GptMarkdown is affected. 2GptMarkdown( 3 text, 4 styleSheet: const GptMarkdownStyleSheet( 5 blockQuote: BlockQuoteStyle(barWidth: 4, barColor: Colors.indigo), 6 codeBlock: CodeBlockStyle( 7 borderRadius: Radius.circular(12), 8 showCopyButton: true, 9 ), 10 latex: LatexStyle(scrollBlockHorizontally: true), 11 ), 12)
All 12 style fields show reference
FieldTypeControls
blockQuoteBlockQuoteStyle?Bar width/colour/radius, background, padding, margin, text style
headingHeadingStyle?Text style overlay, padding, showDivider, divider colour/thickness/padding
linkLinkStyle?Colour, hover colour, decoration, decorationThickness, fontWeight
inlineCodeInlineCodeStyle?Font, fontSizeFactor, colour, background, border, radius, padding, boxHeightStyle
listListStyle?Bullet size/colour/shape, marker text style, indent, gapAfterMarker
checkboxCheckboxStyle?Size, checked/unchecked/check colour, borderRadius, gapAfterBox, interactive
codeBlockCodeBlockStyle?Background, border, radius, padding, font, showLanguageLabel, copy button, labels
tableTableStyle?Border colour/width/radius, cellPadding, header background/text style, row stripe
imageImageStyle?Border radius, padding, fit, maxWidth, maxHeight
hrHrStyle?Thickness, colour, padding
sourceTagSourceTagStyle?Background, text style, size, shape, padding (the [1] citation chip)
latexLatexStyle?Text style, padding, background, border radius, scrollBlockHorizontally

Layer 2 — App-wide via GptMarkdownThemeData

Register GptMarkdownThemeData as a ThemeData extension. The brightness parameter is required so default colours are derived correctly. Light and dark themes each need their own instance.

main.dart
1// App-wide: register GptMarkdownThemeData as a ThemeData extension. 2// Light and dark each need their own — the extension lives on ThemeData. 3MaterialApp( 4 theme: ThemeData.light().copyWith( 5 extensions: [ 6 GptMarkdownThemeData( 7 brightness: Brightness.light, 8 styleSheet: const GptMarkdownStyleSheet( 9 link: LinkStyle(decoration: TextDecoration.none), 10 table: TableStyle(cellPadding: EdgeInsets.all(10)), 11 ), 12 // Legacy heading fields — still work; a styleSheet.heading wins field-by-field. 13 h1: const TextStyle(fontSize: 28, fontWeight: FontWeight.w800), 14 linkColor: Colors.indigo, 15 autoAddDividerLineAfterH1: true, 16 ), 17 ], 18 ), 19 darkTheme: ThemeData.dark().copyWith( 20 extensions: [ 21 GptMarkdownThemeData( 22 brightness: Brightness.dark, 23 linkColor: Colors.lightBlueAccent, 24 ), 25 ], 26 ), 27 home: const MyApp(), 28)

Layer 3 — Scoped via GptMarkdownTheme

GptMarkdownTheme is an InheritedWidget that overrides the ThemeData extension for its subtree. Use it when one screen needs different styling without changing the global theme.

scoped_theme.dart
1// Scoped: wrap specific widgets with GptMarkdownTheme. 2// Takes priority over the ThemeData extension. 3GptMarkdownTheme( 4 gptThemeData: GptMarkdownThemeData( 5 brightness: Brightness.light, 6 h1: const TextStyle(fontSize: 28, fontWeight: FontWeight.w900), 7 linkColor: Colors.deepPurple, 8 hrLineColor: Colors.grey.shade300, 9 hrLineThickness: 1.5, 10 hrLinePadding: const EdgeInsets.symmetric(vertical: 8), 11 ), 12 child: GptMarkdown(content), 13)

Merge & precedence

The merge happens per field, not per object. A widget-level value wins over the theme value for the same field, and any field left unset falls back to the package default.

widget styleSheet field  →  theme styleSheet field  →  package default
merge_example.dart
1// Precedence — per field, not per object: 2// widget styleSheet → theme styleSheet → package default 3// 4// With both set, the blockquote gets barWidth: 4 from the widget 5// AND barColor from the theme. Overriding one value never discards the rest. 6 7MaterialApp( 8 theme: ThemeData( 9 extensions: [ 10 GptMarkdownThemeData( 11 brightness: Brightness.light, 12 styleSheet: const GptMarkdownStyleSheet( 13 blockQuote: BlockQuoteStyle(barColor: Colors.indigo), 14 ), 15 ), 16 ], 17 ), 18 home: GptMarkdown( 19 text, 20 styleSheet: const GptMarkdownStyleSheet( 21 blockQuote: BlockQuoteStyle(barWidth: 4), // barColor still comes from theme 22 ), 23 ), 24)

Legacy GptMarkdownThemeData fields

These fields from v1.1.x are still fully supported. A styleSheet.heading or styleSheet.link value wins over them field-by-field where both are set.

Legacy field reference show reference
FieldTypeNotes
brightnessBrightnessRequired. Drives default colour derivation.
h1 – h6TextStyle?Per-level heading text style. HeadingStyle.textStyle is merged over these.
highlightColorColor?Legacy inline-code chip fill. Equivalent to inlineCode: InlineCodeStyle(backgroundColor: …).
linkColorColor?Default link colour. Superseded by styleSheet.link.color.
linkHoverColorColor?Link hover colour (web). Superseded by styleSheet.link.hoverColor.
hrLineColorColor?Horizontal rule colour. Superseded by styleSheet.hr.color.
hrLineThicknessdouble?Rule stroke width. Superseded by styleSheet.hr.thickness.
hrLinePaddingEdgeInsets?Padding around rules. Superseded by styleSheet.hr.padding.
autoAddDividerLineAfterH1bool?Insert a divider after h1. Superseded by HeadingStyle.showDivider.
inlineCodeInlineCodeStyle?App-wide inline code style (v1.2.0). Partial — unset fields derive from ColorScheme.
styleSheetGptMarkdownStyleSheet?App-wide per-component styles (v1.2.0).

Dark mode needs its own extension

GptMarkdownThemeData lives on ThemeData, so theme: and darkTheme: each need one — with brightness: set to match, or the derived defaults will be wrong.