Row
Places children side by side, each at its own width, and continues on the next line when they run out of width, like words in a paragraph. A child with width="fill" takes the width its line leaves, and justify="between" pushes a line’s children apart. A row can split across pages between lines.
Attributes
Section titled “Attributes”| Attribute | Type | Default | Description |
|---|---|---|---|
align |
string | start |
Vertical alignment of the children on each line: start center end. stretch acts as start |
justify |
string | start |
Where each line’s children sit: start center end, or between: the first at the left edge, the last at the right, and the others evenly between |
gap |
token/pt | — | Gap between items (horizontal and vertical) |
width |
token/pt/fill |
— | Constrain width. fill takes the full width, or what the other children leave when the row is inside another row |
padding |
spacing | — | Inner padding |
background |
color or gradient:name |
— | Background: a color, or a gradient that spans the box |
border |
string | — | Border |
radius |
token/pt | — | Corner radius |
color |
color | inherit | Color of the text inside. A text that sets none takes the nearest box’s. See Text color in a box |
opacity |
0–1 | 1 |
How opaque the box and everything in it is, as one. See Opacity |
shadow |
string | — | A hard-edged shadow under the box: "x y color", with an optional opacity. See Shadow |
font |
string | inherit | Font name |
font-size |
token/pt | inherit | Font size |
paginate |
string | — | Page breaks: no break-before break-after keep-next. See Page breaks |
data-source |
string | — | Dot-path to array — repeats this element per item |
Children that fill the rest
Section titled “Children that fill the rest”A child with width="fill" takes the width its line leaves after the other children. Several fill children on one line share that width equally. A fill child counts as no width when the row breaks its children into lines, so it never moves to the next line: the other children keep their widths, and the fill child gets what is left.
A label and its value, the value at the right edge:
<row gap="m"> <text>Subtotal</text> <text width="fill" align="right">$1,200.00</text></row>L.row({ gap: 'm' }, [ L.text(NoAttr, ['Subtotal']), L.text({ width: 'fill', align: 'right' }, ['$1,200.00']),])L.row(RowAttr(gap='m'), [ L.text(NoAttr, ['Subtotal']), L.text(TextAttr(width='fill', align='right'), ['$1,200.00'])])L::row(new RowAttr(gap: 'm'), [ L::text(NoAttr, ['Subtotal']), L::text(new TextAttr(width: 'fill', align: 'right'), ['$1,200.00']),])L.Row(new() { Gap = "m" }, [ L.Text(NoAttr, ["Subtotal"]), L.Text(new() { Width = "fill", Align = "right" }, ["$1,200.00"]),])The fill child can be first. Here the title takes the space and the date sits at the right edge:
<row gap="m" align="center"> <text width="fill" font-size="14pt">Staff Engineer, Northwind Data</text> <text font-size="9pt" color="#888888">2021 to present</text></row>L.row({ gap: 'm', align: 'center' }, [ L.text({ width: 'fill', fontSize: '14pt' }, ['Staff Engineer, Northwind Data']), L.text({ fontSize: '9pt', color: '#888888' }, ['2021 to present']),])L.row(RowAttr(gap='m', align='center'), [ L.text(TextAttr(width='fill', font_size='14pt'), ['Staff Engineer, Northwind Data']), L.text(TextAttr(font_size='9pt', color='#888888'), ['2021 to present'])])L::row(new RowAttr(gap: 'm', align: 'center'), [ L::text(new TextAttr(width: 'fill', fontSize: '14pt'), ['Staff Engineer, Northwind Data']), L::text(new TextAttr(fontSize: '9pt', color: '#888888'), ['2021 to present']),])L.Row(new() { Gap = "m", Align = "center" }, [ L.Text(new() { Width = "fill", FontSize = "14pt" }, ["Staff Engineer, Northwind Data"]), L.Text(new() { FontSize = "9pt", Color = "#888888" }, ["2021 to present"]),])A bullet: a narrow marker, then text that fills the rest of the line and wraps within it:
<row gap="s"> <text width="8pt">•</text> <text width="fill">Led the design of a streaming pipeline that handles fifty million events a day.</text></row>L.row({ gap: 's' }, [ L.text({ width: '8pt' }, ['•']), L.text({ width: 'fill' }, ['Led the design of a streaming pipeline that handles fifty million events a day.']),])L.row(RowAttr(gap='s'), [ L.text(TextAttr(width='8pt'), ['•']), L.text(TextAttr(width='fill'), ['Led the design of a streaming pipeline that handles fifty million events a day.'])])L::row(new RowAttr(gap: 's'), [ L::text(new TextAttr(width: '8pt'), ['•']), L::text(new TextAttr(width: 'fill'), ['Led the design of a streaming pipeline that handles fifty million events a day.']),])L.Row(new() { Gap = "s" }, [ L.Text(new() { Width = "8pt" }, ["•"]), L.Text(new() { Width = "fill" }, ["Led the design of a streaming pipeline that handles fifty million events a day."]),])Two fill children take half the width each. For two or more equal columns a grid does the same, but a row keeps align:
<row gap="24pt"> <column gap="8pt" width="fill"> <text font-size="s">Bill To</text> <text>Acme Inc</text> <text>123 Main St</text> </column> <column gap="8pt" width="fill"> <text font-size="s">Ship To</text> <text>Acme Inc</text> <text>456 Depot Ave</text> </column></row>L.row({ gap: '24pt' }, [ L.column({ gap: '8pt', width: 'fill' }, [ L.text({ fontSize: 's' }, ['Bill To']), L.text(NoAttr, ['Acme Inc']), L.text(NoAttr, ['123 Main St']), ]), L.column({ gap: '8pt', width: 'fill' }, [ L.text({ fontSize: 's' }, ['Ship To']), L.text(NoAttr, ['Acme Inc']), L.text(NoAttr, ['456 Depot Ave']), ]),])L.row(RowAttr(gap='24pt'), [ L.column(ColumnAttr(gap='8pt', width='fill'), [ L.text(TextAttr(font_size='s'), ['Bill To']), L.text(NoAttr, ['Acme Inc']), L.text(NoAttr, ['123 Main St']) ]), L.column(ColumnAttr(gap='8pt', width='fill'), [ L.text(TextAttr(font_size='s'), ['Ship To']), L.text(NoAttr, ['Acme Inc']), L.text(NoAttr, ['456 Depot Ave']) ])])L::row(new RowAttr(gap: '24pt'), [ L::column(new ColumnAttr(gap: '8pt', width: 'fill'), [ L::text(new TextAttr(fontSize: 's'), ['Bill To']), L::text(NoAttr, ['Acme Inc']), L::text(NoAttr, ['123 Main St']), ]), L::column(new ColumnAttr(gap: '8pt', width: 'fill'), [ L::text(new TextAttr(fontSize: 's'), ['Ship To']), L::text(NoAttr, ['Acme Inc']), L::text(NoAttr, ['456 Depot Ave']), ]),])L.Row(new() { Gap = "24pt" }, [ L.Column(new() { Gap = "8pt", Width = "fill" }, [ L.Text(new() { FontSize = "s" }, ["Bill To"]), L.Text(NoAttr, ["Acme Inc"]), L.Text(NoAttr, ["123 Main St"]), ]), L.Column(new() { Gap = "8pt", Width = "fill" }, [ L.Text(new() { FontSize = "s" }, ["Ship To"]), L.Text(NoAttr, ["Acme Inc"]), L.Text(NoAttr, ["456 Depot Ave"]), ]),])Children pushed apart
Section titled “Children pushed apart”justify="between" puts each line’s first child at the left edge and its last at the right, with the others evenly between. A line with one child starts at the left.
<row justify="between" align="center"> <text font-size="20pt">Invoice</text> <text color="#888888">2026-05-04</text></row>L.row({ justify: 'between', align: 'center' }, [ L.text({ fontSize: '20pt' }, ['Invoice']), L.text({ color: '#888888' }, ['2026-05-04']),])L.row(RowAttr(justify='between', align='center'), [ L.text(TextAttr(font_size='20pt'), ['Invoice']), L.text(TextAttr(color='#888888'), ['2026-05-04'])])L::row(new RowAttr(justify: 'between', align: 'center'), [ L::text(new TextAttr(fontSize: '20pt'), ['Invoice']), L::text(new TextAttr(color: '#888888'), ['2026-05-04']),])L.Row(new() { Justify = "between", Align = "center" }, [ L.Text(new() { FontSize = "20pt" }, ["Invoice"]), L.Text(new() { Color = "#888888" }, ["2026-05-04"]),])Each child keeps its own width, so a child too long for the space left moves to the next line. When a child can be long, such as a paragraph or text from data, give it width="fill" and align="right" instead: it stays on the line and wraps within the space.
Tag / badge row
Section titled “Tag / badge row”<row gap="s"> <frame padding="xs" background="#e8f0fe" radius="xs"> <text font-size="xs" color="#1a73e8">Design</text> </frame> <frame padding="xs" background="#e8f0fe" radius="xs"> <text font-size="xs" color="#1a73e8">Engineering</text> </frame> <frame padding="xs" background="#fce8e6" radius="xs"> <text font-size="xs" color="#c5221f">Urgent</text> </frame></row>L.row({ gap: 's' }, [ L.frame({ padding: 'xs', background: '#e8f0fe', radius: 'xs' }, [ L.text({ fontSize: 'xs', color: '#1a73e8' }, ['Design']), ]), L.frame({ padding: 'xs', background: '#e8f0fe', radius: 'xs' }, [ L.text({ fontSize: 'xs', color: '#1a73e8' }, ['Engineering']), ]), L.frame({ padding: 'xs', background: '#fce8e6', radius: 'xs' }, [ L.text({ fontSize: 'xs', color: '#c5221f' }, ['Urgent']), ]),])L.row(RowAttr(gap='s'), [ L.frame(FrameAttr(padding='xs', background='#e8f0fe', radius='xs'), [ L.text(TextAttr(font_size='xs', color='#1a73e8'), ['Design']) ]), L.frame(FrameAttr(padding='xs', background='#e8f0fe', radius='xs'), [ L.text(TextAttr(font_size='xs', color='#1a73e8'), ['Engineering']) ]), L.frame(FrameAttr(padding='xs', background='#fce8e6', radius='xs'), [ L.text(TextAttr(font_size='xs', color='#c5221f'), ['Urgent']) ])])L::row(new RowAttr(gap: 's'), [ L::frame(new FrameAttr(padding: 'xs', background: '#e8f0fe', radius: 'xs'), [ L::text(new TextAttr(fontSize: 'xs', color: '#1a73e8'), ['Design']), ]), L::frame(new FrameAttr(padding: 'xs', background: '#e8f0fe', radius: 'xs'), [ L::text(new TextAttr(fontSize: 'xs', color: '#1a73e8'), ['Engineering']), ]), L::frame(new FrameAttr(padding: 'xs', background: '#fce8e6', radius: 'xs'), [ L::text(new TextAttr(fontSize: 'xs', color: '#c5221f'), ['Urgent']), ]),])L.Row(new() { Gap = "s" }, [ L.Frame(new() { Padding = "xs", Background = "#e8f0fe", Radius = "xs" }, [ L.Text(new() { FontSize = "xs", Color = "#1a73e8" }, ["Design"]), ]), L.Frame(new() { Padding = "xs", Background = "#e8f0fe", Radius = "xs" }, [ L.Text(new() { FontSize = "xs", Color = "#1a73e8" }, ["Engineering"]), ]), L.Frame(new() { Padding = "xs", Background = "#fce8e6", Radius = "xs" }, [ L.Text(new() { FontSize = "xs", Color = "#c5221f" }, ["Urgent"]), ]),])Dynamic tags from data
Section titled “Dynamic tags from data”<row gap="xs" data-source="tags"> <frame padding="xs" background="#f0f0f0" radius="xs"> <text font-size="xs" data-value="name">Tag</text> </frame></row>Repeating rows with data binding
Section titled “Repeating rows with data binding”<column data-source="items" gap="xs"> <row> <text data-value="description">Item description</text> <text width="fill" data-value="price" align="right">$0.00</text> </row></column>