Guide
Layout and styling
How ngpdf lays out pdf-* elements with flexbox through Yoga, how to write styles and bindings, and which units, colors and borders work.
ngpdf lays elements out with flexbox, computed by Yoga. You style elements with CSS declarations in the style attribute or with [style.*] bindings. There are no stylesheets and no classes.
Writing styles
Three forms work, on any pdf-* element:
<!-- a static style attribute -->
<pdf-view style="flex-direction: row; gap: 12; padding: 8">…</pdf-view>
<!-- a binding per property, with an optional unit suffix -->
<pdf-view [style.background-color]="highlight() ? '#fff3cd' : null" [style.width.%]="progress()" />
<!-- an object binding -->
<pdf-text [style]="{ color: '#c62828', 'font-weight': 'bold' }">Overdue</pdf-text>You can combine a static style with bindings on the same element. As in a browser, the last declaration of a property wins, and a binding wins over the static style. So margin: 10; margin-top: 5 gives a top margin of 5, and so does style="margin: 10" with [style.margin-top]="5". A binding set to null removes that property. One set to undefined falls back to the value from a [style] object binding or the static style, if either sets it, and otherwise removes it. Property names can be kebab-case or camelCase (backgroundColor becomes background-color).
What has no effect:
classand[class.*]bindings, and the component'sstylesorstyleUrl. They are CSS for the DOM, and a document has no DOM.- Styles on the host element of your own components. ngpdf ignores them with a dev-mode warning. See Components in documents.
- Properties ngpdf does not know, such as
z-index,orderorfont. They are ignored without a warning.
The style attribute is split on ; outside quotes and parentheses, so a value such as url(data:image/png;base64,...) or "A;B" stays whole. Keywords are matched in any case, as in CSS: text-align: CENTER is center and font-weight: BOLD is bold. !important and var() are not supported.
Flexbox
Every element is a flex container. The defaults follow CSS flexbox, with one difference: the default direction is column, as in React Native, except for pdf-row, which defaults to row.
| Property | Values | Default |
|---|---|---|
flex-direction |
row, column |
column (row on pdf-row) |
flex-wrap |
nowrap, wrap, wrap-reverse |
nowrap |
flex-flow |
<direction> <wrap>, either part optional, in any order |
|
justify-content |
flex-start, center, flex-end, space-between, space-around, space-evenly |
flex-start |
align-items |
stretch, flex-start, center, flex-end |
stretch |
align-self |
auto, stretch, flex-start, center, flex-end |
auto |
align-content |
stretch, flex-start, center, flex-end, space-between, space-around, space-evenly |
stretch |
gap |
<row> [<column>], lengths or %; one value sets both |
0 |
row-gap |
length, % |
0 |
column-gap |
length, % |
0 |
flex |
<grow> [<shrink>] [<basis>], auto, none |
|
flex-grow |
number | 0 |
flex-shrink |
number | 1 |
flex-basis |
length, %, auto |
auto |
flex: 1 means grow 1, shrink 1, basis 0, as in CSS. row-reverse, column-reverse, order and baseline are not supported.
row-gap is the space between the children of a column, and column-gap the space between the children of a row. Without flex-wrap, a column uses only its row gap and a row only its column gap, as in a browser. A wrapping row also puts its row gap between its lines. A percentage is of the container's content box: its height for row-gap, its width for column-gap. A negative gap throws. When a column breaks across pages, the row gap before the first child on the new page is dropped: that child starts at the top of the content area.
Sizing uses width, height, min-width, min-height, max-width and max-height, plus margin and padding with one to four values, or per side (margin-top, padding-left, and so on).
A margin can be auto, as in CSS: auto margins take the free space on their side. margin: 0 auto centres a box that has a width, and margin-left: auto pushes an item to the end of a row. padding has no auto and throws on it.
<pdf-view
style="flex-direction: row; justify-content: space-between; align-items: center; gap: 12; padding-bottom: 8; border-bottom: 1 solid #cccccc"
>
<pdf-text style="font-size: 18; font-weight: bold">Order #{{ orderId() }}</pdf-text>
<pdf-text style="color: #666666">Shipped</pdf-text>
</pdf-view>
<pdf-view style="flex-direction: row; gap: 12; margin-top: 12">
<pdf-view style="flex: 2; padding: 8; background-color: #f4f6f8; border-radius: 4">
<pdf-text>Shipping address</pdf-text>
</pdf-view>
<pdf-view style="flex: 1; padding: 8; border: 1 solid #dddddd">
<pdf-text>Notes</pdf-text>
</pdf-view>
</pdf-view>A page has no fixed height during layout: content is laid out at the page's content width with unbounded height, then cut into sheets. See Pagination.
Wrapping
With flex-wrap: wrap, a row puts the children that don't fit on further lines below, as in a browser. wrap-reverse stacks the lines upwards instead. flex-flow sets the direction and the wrap together; as in CSS, the part it leaves out goes back to its default, so write flex-flow: row wrap for a row, since flex-flow: wrap alone gives a wrapping column.
<pdf-view style="flex-flow: row wrap; gap: 8">
@for (tag of tags(); track tag) {
<pdf-text style="padding: 4 8; border: 1 solid #cccccc; border-radius: 4">{{ tag }}</pdf-text>
}
</pdf-view>align-content places the lines when the container has free space on the cross axis, for example a row with a fixed height. The default, stretch, shares that space out between the lines, as CSS's normal does; Yoga's own default is flex-start. A column wraps only when its height is limited, by height, max-height or flex in a parent with a fixed height.
Each line of a wrapping row is its own unit for pagination, so a long grid breaks between lines. A pdf-row in a table never wraps: flex-wrap there is ignored with a warning.
Units
| Unit | Meaning |
|---|---|
| none | points. 12 is 12pt. |
pt |
points, 1/72 inch |
px |
1px = 1pt, also on a page with a dpi |
in |
72pt |
cm |
72/2.54pt |
mm |
72/25.4pt |
% |
of the parent's content box |
Any other unit, such as em, rem or vh, throws ngpdf: unknown unit in "<value>".
Numbers can have an exponent, as in CSS: 1e-7, 1.5E3, 2e1mm. A bound number is turned into text with String(n), which writes values below 1e-6 and from 1e21 up this way, so [style.left.pt]="x" works for any finite x. A number too large to represent, such as 1e999, or one that becomes too large once its unit is applied, such as 1e308in, throws; in a length the error is ngpdf: invalid length "1e999" (not a finite number).
Percentages work in width, height, the min-/max- sizes, margin, padding and flex-basis. gap, font-size, border-radius and border widths take a length only; a percentage there throws.
line-height is the exception to "a bare number is points": there it multiplies the font size, as in CSS. See Text and fonts.
Colors
color, background-color and the border colors accept:
- hex with 3, 4, 6 or 8 digits:
#333,#3338,#333333,#33333380; rgb()andrgba(), with commas or spaces and a slash, numbers or percentages:rgb(51, 51, 51),rgb(51 51 51 / 50%),rgba(0, 0, 0, 0.5);- the CSS named colors, in any case:
steelblue,Navy; transparent.
Anything else, such as hsl(), currentColor or var(--brand), throws ngpdf: invalid color "<value>". An alpha channel is drawn as opacity.
Backgrounds, borders and radius
background-colorfills the element's border box.background-imagetakes onelinear-gradient()orradial-gradient(), ornone. The gradient fills the border box over the background color, followsborder-radius, and is not inherited.backgroundis a shorthand for a color, a gradient, or one of each:background: linear-gradient(to right, #dd0031, #c3002f) #dd0031.border: <width> <style> <color>, in any order, with every part optional. The width defaults to 1, the style tosolidand the color to black.noneandhiddenremove the border.- Per side:
border-top,border-right,border-bottom,border-left, andborder-<side>-width,border-<side>-style,border-<side>-color.border-width,border-styleandborder-colortake one to four values, likemargin. - The styles drawn are
solid,dashedanddotted. A side whose style isnoneorhiddenis not drawn and has a width of 0 in the layout, as in CSS, whatever order the declarations come in.double,groove,ridge,insetandoutsetare drawn solid, with a dev-mode warning. Unlike CSS, a border with no style is solid, soborder-width: 2alone draws one. dasheddraws dashes 3 times the width long with gaps of about the same length.dotteddraws round dots as wide as the border, their centres about 2 widths apart. The gaps are fitted to each side, so a side starts and ends with a dash or a dot at its corners. On a square corner the dot sits in the corner, inside the box. A side too short for two dashes is drawn solid.- When all four sides of a rounded box are dashed or dotted with the same width and color, the whole outline is drawn as one line, so the dashes or dots run on round the corners. Otherwise each side starts its own pattern at its corner.
- The width keywords
thin,mediumandthickmean 1, 3 and 5 points, inborder,border-widthand the per-side forms. border-radiustakes one to four lengths, from the top-left corner clockwise, as in CSS.border-top-left-radius,border-top-right-radius,border-bottom-right-radiusandborder-bottom-left-radiusset one corner. The background and the borders both follow the rounded corners. When the radii on a side add up to more than its length, all four are scaled down together.- Where two border sides of different widths, styles or colors meet at a rounded corner, each keeps its own up to the middle of the arc.
- A view split across pages keeps square corners where the page cuts it.
- Children and images of a rounded view are not clipped to its corners unless it has an
overflowother thanvisible.
Overflow
By default, content that does not fit its box is drawn outside it. overflow: hidden clips it:
<pdf-view style="overflow: hidden; height: 120; border: 1 solid #ccc; border-radius: 8">
<pdf-image [src]="photo" style="height: 160; object-fit: cover" />
<pdf-text>{{ caption() }}</pdf-text>
</pdf-view>- The element's children, text and image are clipped to its padding box, inside the border. Its own background and border are not clipped.
- With
border-radius, the clip is rounded too. Each corner's radius is the outer radius less the wider of the two borders beside it, never below 0. clip,scrollandautoclip likehidden.overflow-xandoverflow-yare ignored.- Nested clipping elements clip to the area they share.
- The layout does not change: sizes and positions are the same with or without clipping.
- A column
pdf-viewor apdf-textwithoverflow: hidden,clip,scrollorautois one pagination unit, so it is never split across sheets. One taller than a sheet is cut at the bottom, with a dev-mode warning, and the rest is not drawn. overflowis not inherited, and is ignored on apdf-page(a page is not a box to clip) and on apdf-table, which keeps breaking between its rows.
Gradients
linear-gradient() takes an optional direction, then two or more color stops:
- the direction is an angle in
deg,radorturn(0degpoints up,90degright), ortowith a side or a corner:to right,to top left. The default isto bottom. The gradient line is as long as CSS makes it, so45degandto top rightreach the corners; - a stop is a color with an optional position in
%of the gradient line or a length:#dd0031 20%,white 40pt. Stops without a position are spread evenly between their neighbours, and a position smaller than an earlier one is raised to it, as in CSS. Two stops at one position make a hard edge.
radial-gradient() takes an optional shape, size and center, then the stops:
- the shape is
circleorellipse(the default); - the size is
closest-side,closest-corner,farthest-sideorfarthest-corner(the default); - the center is
atone or two values, keywords (left,center,right,top,bottom), lengths or percentages of the box. The default isat center; - stop positions are measured along the horizontal radius.
<pdf-view style="padding: 12; background: radial-gradient(circle at top left, #ffffff55, transparent 60%) #1f2a44">
<pdf-text style="color: white">Quarterly report</pdf-text>
</pdf-view>A stop's alpha is its opacity; the element's opacity fades the whole box on top of it (see Opacity). A transparent stop takes the color of its neighbours, so linear-gradient(red, transparent) fades the red out instead of darkening it. A view split across pages keeps one gradient for its whole height: each sheet shows its part, like box-decoration-break: slice.
These throw an ngpdf: error: repeating-linear-gradient(), repeating-radial-gradient(), conic-gradient(), url(), several comma-separated backgrounds, explicit radial sizes such as circle 40px, color hints (red, 30%, blue) and stops with two positions.
Opacity
opacity is a number from 0 to 1 (values outside are clamped). It fades an element and everything inside it as one picture, as in CSS: the content is drawn first, then the whole is made see-through. Overlapping children of a faded view don't show through each other, and text over a background in a faded card stays solid over that background. A faded view inside another faded view composes, so 0.5 inside 0.5 ends up at 0.25 over the page.
<pdf-view style="opacity: 0.5; padding: 12; background-color: #1f2a44">
<pdf-text style="color: white">Draft</pdf-text>
</pdf-view>A faded element split across sheets is faded on each sheet, and so is a fixed view or a repeated table header. opacity: 0 draws nothing, but links and anchors inside still work. A color's own alpha, such as rgba(0, 0, 0, 0.5), still applies to what it paints, inside the faded element.
Each faded element adds a small object to the file (a PDF transparency group). Fonts and images inside it are shared with the page, so nothing is embedded twice.
Transforms
transform rotates, scales and moves an element when it is drawn, as in CSS:
<pdf-view
style="transform: rotate(-12deg); align-self: flex-end; padding: 6 12; border: 2 solid #c62828; border-radius: 4"
>
<pdf-text style="color: #c62828; font-size: 18; font-weight: bold">PAID</pdf-text>
</pdf-view>- The functions are
rotate(),scale(),scaleX(),scaleY(),translate(),translateX()andtranslateY(), applied left to right. Function names are matched in any case. - Angles take
deg,radorturn. A positive angle turns clockwise. scale(2)scales both axes andscale(2, 0.5)each one. Translations take lengths, or a percentage of the element's own border box.transform-origintakes one or two values:left,center,right,top,bottom, lengths or percentages of the border box. It defaults tocenter.- The whole element is transformed: its background, border, children, text and image. Its
overflowclip turns with it. - The layout does not change: sizes and positions are the same with or without a transform, and nothing around the element moves.
- A transformed element can be drawn outside its box, past the page margins or off the sheet. Only an ancestor with an
overflowother thanvisibleclips it. - A column
pdf-viewwith a transform and a transformedpdf-textare each one pagination unit, so a rotated box is never cut across sheets. One taller than the sheet's content area is cut at the bottom, with a dev-mode warning. - A
pdf-spanis part of a line, sotransformandtransform-originhave no effect on it. - Neither property is inherited.
transformis ignored on apdf-pageand on apdf-table, which keeps breaking between its rows; its value is still checked. skew(),matrix()and the 3D functions throw, like any unknown function.
Positioning
position takes an element out of its place in the flow, as in CSS. A stamp in the corner of a card:
<pdf-view style="position: relative; padding: 16; border: 1 solid #ccc">
<pdf-text>Invoice 2026-114</pdf-text>
<pdf-view
style="position: absolute; top: 8; right: 8; padding: 2 6; background-color: #2e7d32"
>
<pdf-text style="color: white; font-size: 8; font-weight: bold">PAID</pdf-text>
</pdf-view>
</pdf-view>positionisstatic(the default),relativeorabsolute.fixedandstickythrow: for a header or footer on every sheet, use a fixed view.top,right,bottomandlefttake a length, a percentage orauto(the default). They do nothing on astaticelement.- A
relativeelement is drawn moved by its offsets, with everything it holds. The layout does not change: its siblings stay where they were.topwins overbottomandleftoverright, and a percentage is of the parent's content box. - An
absoluteelement leaves the flow: it takes no space, and its parent's size ignores it. It is placed against the padding box of its nearestrelative,absoluteor transformed ancestor, its containing block, or against the content area of itspdf-pagewhen there is none. A percentage is of that padding box. - With
leftandrightboth set andwidth: auto, an absolute element stretches between them, and the same goes fortop,bottomand the height. Otherwise it takes itswidthandheight, or the size of its content. - Positioned elements are drawn after the in-flow ones, so they sit on top of them. Positioned elements of the same containing block, at any depth, are drawn in template order. The exception is a
relativeelement inside a static element with anoverflowclip: it is drawn with that element, inside its clip, before any absolute element that escapes the clip. An absolute element is drawn with its containing block: inside itsoverflowclip and its transform. A static clipping element between them does not clip it, as in CSS. - An absolute element is never split across sheets; Pagination says where it goes.
z-indexis not supported. None of these properties is inherited. They are ignored onpdf-page,pdf-rowandpdf-cell, whose values are still checked, and have no effect on apdf-span.
Hiding elements
display: none leaves an element out of the document, with everything inside it, the same as wrapping it in @if:
<pdf-view [style.display]="order().notes ? null : 'none'">
<pdf-text style="font-weight: bold">Notes</pdf-text>
<pdf-text>{{ order().notes }}</pdf-text>
</pdf-view>- A hidden element takes no space, starts no new sheet, draws nothing and loads none of its images or fonts. That includes the absolute elements, fixed views, notes, images, canvases and
pdfPerSheettemplates inside it. - An
anchoror abookmarkinside a hidden element does not exist: the bookmark is left out of the outline, and apdf-linkto the anchor gets the same dev-mode warning as a link to a name no element declares,ngpdf: pdf-link href "#notes" names no anchor; no link written. Its content is drawn without a link. - A hidden
pdf-spanor inlinepdf-linkdrops out of its line. A hiddenpdf-celltakes no column, so the cells after it move one column to the left. A hiddenpdf-pageadds no sheets, and when every page is hidden the render throwsngpdf: pdf-document has no pdf-page. display: flexis the default. Any other value, such asblock,gridorcontents, throws:ngpdf: invalid value "block" for display on pdf-view; use flex or none. The values inside a hidden element are not checked.displayis not inherited, and on a component's host element it is ignored with the other host styles.
Debugging layouts
When a box is not where you expect it, turn on debug on pdf-document and look at the PDF:
<pdf-document [debug]="debug()">
<pdf-page style="padding: 36">
<pdf-view style="padding: 12; border: 1 solid #ccc">
<pdf-text>Where does this end?</pdf-text>
</pdf-view>
</pdf-page>
</pdf-document>A bare debug attribute turns it on too. Bind it to an input or a signal so it is on only while you check a layout. Every box then gets outlines drawn on top of the page:
- Each
pdf-view,pdf-text,pdf-image,pdf-table,pdf-row,pdf-cell,pdf-linkandpdf-page-numbergets a 0.5pt red outline around its border box. - When the box has padding or a border, its content box gets a blue dashed outline. The gap between the red and the blue line is the padding plus the border.
- A grey label in 4pt Helvetica sits in the top left corner: the tag without
pdf-and the size in points, such asview 120×40. A box smaller than its label gets no label. - The content area of the page, the sheet less its padding, gets a green dashed outline.
- Each line of a
pdf-textgets a thin grey outline, which shows the line height and where the text wrapped. - An element of size 0 by 0 gets a small red cross where it sits.
What debug does and does not change:
- The layout and the page breaks stay the same as without
debug. Only the outlines are added. - The outlines are drawn after everything else on the sheet, so no background hides them. They turn with a transform, and an
overflowclip does not cut them, so a clipped box still shows its whole outline. - A box split across sheets is outlined piece by piece on each sheet. A repeated table header and a fixed view are outlined on every sheet. Absolute elements are outlined where they land.
- The labels use a standard font, so they work without any custom font.
- Each render with
debugon prints one dev-mode warning,ngpdf: debug outlines are on, so a debug PDF does not get shipped by mistake. Labels are real text, so they show up when the PDF is searched or copied.
Inheritance
Text styles are inherited from parent to child: color, font-family, font-size, font-weight, font-style, line-height, text-align, text-decoration, letter-spacing, text-transform, hyphens, orphans and widows. Set them on a pdf-page, a pdf-view or a pdf-cell and every text inside follows. No other property is inherited.
What throws
Invalid values for the properties ngpdf knows throw an Error whose message starts with ngpdf: and names the element, for example ngpdf: invalid value "middle" for text-align on pdf-text; use left, center, right or justify. The render rejects with it, so check the console when a render fails. Every property that takes keywords throws on one it doesn't know, and the message lists the ones it takes. Unknown properties never throw. The full list of properties and values is in Style properties.