Style HTML with CSS
At a glance
Section titled “At a glance”This recipe applies Cascading Style Sheets (CSS) to Hypertext Markup Language (HTML) content. It uses inline style attributes and a <style> block. You also learn how to confirm that a property is verified before you rely on it.
Install
Section titled “Install”composer require nextpdf/core:^3Use this constraint for the nextpdf/core package. The example runs on PHP 8.4.
Conceptual overview
Section titled “Conceptual overview”The HTML engine resolves styles through the CSS cascade. The cascade decides which style wins when several rules target the same element. The engine reads the input <style> block first, then the inline style attributes. It matches both against elements and computes the result in cascade order. The support matrix grades the CSS Cascading and Inheritance module as Verified, which means a dedicated fixture suite covers cascade and inheritance behavior.
Text presentation properties such as text-align, text-indent, and letter-spacing are defined in the World Wide Web Consortium (W3C) CSS Text module property table (W3C CSS Text Level 3). Font selection uses the CSS Fonts model. The font shorthand sets font-style, font-variant, font-weight, font-size, and font-family together (W3C CSS Fonts Level 3).
Support is tracked by W3C module and truth-audited. A property can be implemented and still lack a dedicated module fixture; in that case, it is graded “Claimed”, not “Verified”. Treat “Claimed” as best effort, and validate the visual result for your content. Treat “Verified” as backed by a passing fixture suite.
API surface
Section titled “API surface”You control styling through the HTML you pass to writeHtml(string $html): static (NextPDF\Core\Concerns\HasTextOutput). There is no separate application programming interface (API) for CSS; the cascade runs internally. The full PHPDoc table is generated from source.
Code sample — Quick start
Section titled “Code sample — Quick start”<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->addPage();
$doc->writeHtml( '<style>.lead { color: #1E3A8A; text-align: center; }</style>' . '<p class="lead">Styled with a style block.</p>');
$doc->save(__DIR__ . '/out.pdf');Code sample — Production
Section titled “Code sample — Production”This self-contained example can run in the harness. It exercises a <style> block, class selectors, inline styles, and the font family and weight path.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->setTitle('CSS Styling');$doc->addPage();
$html = <<<'HTML'<style> h1 { color: #1E3A8A; } .lead { color: #D97706; font-size: 14px; } .callout { background-color: #EFF6FF; padding: 8px; border: 1px solid #BFDBFE; } .centered { text-align: center; }</style>
<h1>Styling HTML with CSS</h1>
<p class="lead">This paragraph is styled through a class selector in astyle block.</p>
<p class="callout">This paragraph has a background, padding, and a borderfrom a class selector.</p>
<p class="centered" style="font-weight: bold;">Centered, with an inlineoverride for weight.</p>
<p style="color: #6B7280; font-size: 9px;">Inline style only.</p>HTML;
$doc->writeHtml($html);
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');$doc->save($out !== false ? $out : __DIR__ . '/style-with-css.pdf');
echo "Wrote style-with-css.pdf\n";Expected standard output (STDOUT):
Wrote style-with-css.pdfEdge cases & gotchas
Section titled “Edge cases & gotchas”- Cascade order. In the cascade, an inline
stylewins over a<style>block selector with equal specificity. If you see an unexpected color, check for an inline override first. - Claimed vs Verified.
text-align,text-indent, andcolorare graded “Claimed” in the matrix. They are implemented, but they have no dedicated module fixture. They render, but you should validate the visual output before production sign-off. - No retained tree. The streaming engine, described in Architecture Decision Record (ADR-001), keeps no document tree, so it cannot apply a selector where a later sibling restyles an earlier element. As a result, order-independent selectors are constrained.
- Unsupported property. An unsupported property is ignored, not raised as an error. Check the matrix before you rely on it.
- Color spaces. Lab, LCH, and OKLab values parse, but Portable Document Format (PDF) color-space fidelity for those functions is not asserted (matrix: “Claimed” for CSS Color 4).
Performance
Section titled “Performance”Cascade resolution happens in the single pass. Cost stays linear in token count, O(n). The budget is wall_ms: 1500, peak_mb: 96. A <style> block adds a one-time selector-parse cost proportional to the rule count.
CSS support matrix excerpt (Verified-only rows)
Section titled “CSS support matrix excerpt (Verified-only rows)”This excerpt shows only the Verified rows from the truth-audited CSS support matrix.
| W3C module | Level | Status | Evidence |
|---|---|---|---|
CSS Cascading and Inheritance (css_cascade_3) | 3 | Verified | src/Html/Cascade/, tests/Unit/Html/Cascade/ |
CSS Cascading (css_cascade_4) | 4 | Verified | Cascade/Layer/ + revert/layer tests |
CSS Cascading (css_cascade_5) | 5 | Verified | AtRule/Layer/ + Cascade/Layer/ suites |
CSS Fonts (css_fonts_3) | 3 | Verified | src/Html/Font/, tests/Unit/Font/ + FontResolver tests |
CSS Fonts (css_fonts_4) | 4 | Verified | src/Html/FontFace/, tests/Unit/Html/FontFace/ |
text-align, text-indent, color, and background-color are “Claimed” in the matrix. The table omits them from the Verified rows on purpose.
Single-pass streaming constraints (ADR-001)
Section titled “Single-pass streaming constraints (ADR-001)”The cascade runs over a streamed token list with no Document Object Model (DOM), so selectors resolve against document-order context. Full-tree selector cases are constrained per ADR-006. Write CSS that depends only on document order and ancestor context.
Layer contracts (ADR-010)
Section titled “Layer contracts (ADR-010)”CSS parsing belongs to the cascade layer, and layout dispatch must not read raw $css[...] values directly. The public surface is the HTML input. There is no supported way to inject computed styles past the cascade.
Memory budget for large documents
Section titled “Memory budget for large documents”The cascade holds the active rule set and the push and pop style stack, not a node tree. Container-scoped contexts obey the ADR-020 budget (5,000 nodes per context, 50 MB active ceiling). Large stylesheets grow rule-set memory linearly, so keep selector counts bounded.
Security notes
Section titled “Security notes”CSS in untrusted HTML cannot run code, but it can affect layout and resource references, such as background-image. The default external-resource policy does not fetch arbitrary remote URLs. Sanitize any HTML and CSS you assemble from user input before you render it.
Conformance
Section titled “Conformance”| Statement | Spec | Clause | reference_id |
|---|---|---|---|
| Text properties (text-align, text-indent, letter-spacing) are defined by the CSS Text property table. | W3C CSS Text Level 3 | css_text_3#x1.x24 | |
| The font shorthand sets font-style, font-variant, font-weight, font-size, and font-family. | W3C CSS Fonts Level 3 | css_fonts_3#x2.x6.x7.p2 |
This recipe shows how NextPDF applies a supported CSS subset; it does not assert full CSS support. For verified per-module status, see the CSS support matrix.
Commercial context
Section titled “Commercial context”Not applicable.