Documentation
Components
Every component maps to one engine element, and its props are typed - <Text :size :color bold>
autocompletes and type-checks, and a wrong value (say a fit that does not exist) goes red in your
editor. Boolean props have a shorthand: <Text bold> is <Text :bold="true">.
The full set:
Document · Page · Column · Row · Box · Padding · Expanded · Spacer · Divider ·
Image · Text · Paragraph · Span · Table / TableRow / TableCell · Positioned ·
DefaultTextStyle · Link · Anchor · Bookmark · Rotated · RotatedBox · PageNumber ·
PageCount.
Structure
Document
The root of every PDF component. Its text props (font, size, color, lineHeight, align, bold,
italic) set the document-wide defaults that everything inside inherits.
| Prop | Type |
|---|---|
size, color, font, bold, italic | default text style |
align, lineHeight | default paragraph style |
meta | { title?, author? } |
fonts | custom fonts, see Data & assets |
Page
One physical page. Lays its children out as a column, so gap, justify and align work here too.
| Prop | Type |
|---|---|
size | "A4", "Letter", ... or a custom size |
orientation | "portrait" | "landscape" |
margin | a number, or per-side insets |
gap | space between children |
justify | "start" | "center" | "end" | "between" | "around" |
align | "start" | "center" | "end" | "stretch" |
<Page> also takes #header and #footer slots. They are drawn on every physical page the content
flows onto, and rebuilt per page - which is how <PageNumber> knows where it is:
<template>
<Document>
<Page :size="'A4'" :margin="48">
<template #header>
<Text :size="9" color="#94a3b8">ACME GmbH - Invoice</Text>
</template>
<Text>... long content that paginates across pages ...</Text>
<template #footer>
<Row :justify="'between'">
<Text :size="9" color="#94a3b8">acme.example</Text>
<Text :size="9" color="#94a3b8">Page footer</Text>
</Row>
</template>
</Page>
</Document>
</template>
Layout
Column and Row
Stack children vertically (Column) or horizontally (Row). Both share gap, justify and align.
<template>
<Row :gap="12" :justify="'between'" :align="'center'">
<Text bold>Item</Text>
<Text>Value</Text>
</Row>
</template>
Box
A sized, decorated container: background, border, padding, rounded corners.
| Prop | Type |
|---|---|
bg, border | a color |
borderTop / borderRight / borderBottom / borderLeft, borderWidth | per-side border control |
padding | a number, or per-side insets |
width, height, radius | numbers (points) |
relative | make it a positioning frame |
overflow | "hidden" clips children to the box |
Padding, Spacer, Expanded, Divider
<Padding :insets="16">- wraps a child in space (a number or per-side insets).<Spacer :flex="1">- flexible empty space that pushes siblings apart.<Expanded :flex="1">- a flex child that fills the remaining main-axis space.<Divider color="#e2e8f0" :thickness="1" />- a rule line.
Text
Text, Paragraph and Span
Text and Paragraph render text; Span styles a run inside them.
| Prop | Type |
|---|---|
size, font, color | the basics |
bold, italic | boolean shorthands |
align | "left" | "center" | "right" |
lineHeight | line spacing multiplier |
maxLines, overflow | overflow is "clip" | "ellipsis" |
href | link this run to an external URL |
to | link this run to an <Anchor> |
<template>
<Paragraph>
Plain text, then a <Span bold color="#1450aa">styled run</Span> in the middle.
</Paragraph>
</template>
bold and italic are left unset by default, so a <Text> inside a bold <DefaultTextStyle> stays
bold unless you override it. <DefaultTextStyle> re-defaults the text style for a whole subtree, the
per-section counterpart to the <Document> defaults.
Putting href (or to) on a <Span> links just that run and lets the sentence flow around it:
<template>
<Paragraph>
Built with <Span href="https://jasy.dev" bold color="#1450aa">jasy</Span>, and this part is
not a link.
</Paragraph>
</template>
Image
<template>
<Image :src="bytes" :width="120" :height="120" :fit="'contain'" :radius="8" />
</template>
fit is "none", "contain", "cover" or "fill". src takes image bytes (a Uint8Array); see
Data & assets for loading them in the browser.
Table
<Table :columns> holds <TableRow>s of <TableCell>s. Mark a row header to repeat it on every page
the table paginates onto. Columns are widths: a number is fixed points, a string like "1fr" is a
flexible share.
<template>
<Table :columns="['1fr', 120]" cell-border="#e2e8f0" :cell-padding="9">
<TableRow header>
<TableCell><Text bold>Description</Text></TableCell>
<TableCell><Text bold>Amount</Text></TableCell>
</TableRow>
<TableRow v-for="line in lines" :key="line.id">
<TableCell
><Text>{{ line.name }}</Text></TableCell
>
<TableCell
><Text>{{ line.total }} EUR</Text></TableCell
>
</TableRow>
</Table>
</template>
Table also takes gap / rowGap / colGap, cellPadding, cellBorder and a rule color.
Positioned
Out-of-flow placement, anchored to the nearest <Box relative> (or the page). Give it edges
(top / right / bottom / left) or an alignment (h / v) plus an x / y offset.
<template>
<Box relative :height="200">
<Positioned :top="8" :right="8">
<Text :size="9" color="#94a3b8">draft</Text>
</Positioned>
</Box>
</template>
Navigation
<Link> makes its child clickable. href opens a URL, to jumps to an <Anchor> in the same
document. Exactly one of the two.
<template>
<Link href="https://jasy.dev">
<Box bg="#f3dc29" :padding="12"><Text>The whole box is the hit area</Text></Box>
</Link>
</template>
<Anchor name> marks a jump target, and <Bookmark title> adds an entry to the outline tree your
viewer shows in its sidebar. level nests it under the nearest preceding smaller level.
| Component | Props |
|---|---|
<Link> | href | to |
<Anchor> | name (required) |
<Bookmark> | title (required), level |
A chapter heading usually wants both: a sidebar entry and a jump target. Nest them.
<template>
<Bookmark title="2. Installation" :level="1">
<Anchor name="install">
<Text :size="22" bold>2. Installation</Text>
</Anchor>
</Bookmark>
</template>
See Navigation for how links, anchors and bookmarks work under the hood.
Rotated and RotatedBox
<Rotated :angle> spins its child at any angle around its center, at paint time: the layout box stays
unrotated, so siblings do not reflow. That is what you want for a stamp or a watermark.
<RotatedBox :turns> does layout-aware quarter-turns instead. A 90 or 270 degree turn swaps width and
height, so a vertical label reserves exactly the strip it occupies and its neighbours flow around it.
turns counts clockwise 90-degree steps.
<template>
<Row :gap="12">
<RotatedBox :turns="3"><Text bold>SECTION A</Text></RotatedBox>
<Column><Text>Line one</Text><Text>Line two</Text></Column>
</Row>
<Rotated :angle="-18">
<Box bg="#ffe9e9" border="#d33" :padding="10"><Text :size="26" bold color="#d33">PAID</Text></Box>
</Rotated>
</template>
Page numbers
<PageNumber> and <PageCount> print the current page and the document total. Put them anywhere - a
#footer is the usual home, but the body works too. They take the <Text> styling props plus offset,
which is added to the number (use -1 when a cover page should not count).
<template>
<Document>
<Page>
<template #footer>
<Row justify="center" :gap="3">
<Text :size="9">Page</Text>
<PageNumber :size="9" bold />
<Text :size="9">of</Text>
<PageCount :size="9" bold />
</Row>
</template>
<Column><!-- body --></Column>
</Page>
</Document>
</template>
Getting the total right is why jasy paginates the whole document before it draws anything.
The core also ships a
PageBuilderprimitive, which hands youpageNumber,pageCountandpageSizeand draws whatever you return. It takes a closure, so it is not exposed as a component - a template cannot express one. In a Nuxtserver/route, where you write plain JavaScript, you can use it directly.