Table
A data table, built from NswTableRow and NswTableCell. It has no look of its own beyond table in lib/table.ts — body text, a little padding, no fills or borders: every style prop set here applies to every cell, a row's over it, a cell's over that, so each design is written where it is used. The legacy's basic table, data list and schedule are drawn this way by the demos under emails/table/.
Examples
Data list
Label and value pairs, each label its row’s header, divided by lines.
36.4 KB built. Open the built email (opens in a new tab)
const themes = [
{ key: 'grey', strong: 'grey-800', muted: 'grey-700', line: 'grey-200', tint: 'grey-50' },
{
key: 'primary',
strong: 'primary-800',
muted: 'primary-700',
line: 'primary-200',
tint: 'primary-50',
},
{
key: 'accent',
strong: 'accent-800',
muted: 'accent-700',
line: 'accent-200',
tint: 'accent-50',
},
] as const
function TablePage({
title,
table,
}: {
title: string
table: (theme: (typeof themes)[number]) => ReactNode
}) {
return (
<NswLayout title={title}>
{themes.map((theme) => (
<Fragment key={theme.key}>
<NswColumns padding={false} background='white'>
<NswColumn>
<NswSpacer height={32} />
</NswColumn>
</NswColumns>
<NswColumns background='white'>
<NswColumn>{table(theme)}</NswColumn>
</NswColumns>
<NswColumns padding={false} background='white'>
<NswColumn>
<NswSpacer height={32} />
</NswColumn>
</NswColumns>
<NswColumns padding={false} background='grey-100'>
<NswColumn>
<NswSpacer height={24} />
</NswColumn>
</NswColumns>
</Fragment>
))}
</NswLayout>
)
}
const items: ReadonlyArray<{ label: string; value: string; href?: string }> = [
{ label: 'Event Name:', value: 'NSW Digital Government Summit' },
{ label: 'Date:', value: 'March 15, 2025' },
{ label: 'Location:', value: 'International Convention Centre Sydney' },
{ label: 'Registration:', value: 'Open until March 10, 2025' },
{
label: 'Contact:',
value: 'events@digital.nsw.gov.au',
href: 'mailto:events@digital.nsw.gov.au',
},
];
<TablePage
title='Data list'
table={({ strong, muted, line, tint }) => (
<NswTable
type='body-sm'
lineHeight={16}
padding={12}
color={strong}
background='white'
stripeBackgroundColor={tint}
borders={['bottom']}
borderColor={line}
>
{items.map(({ label, value, href }) => (
<NswTableRow key={label}>
<NswTableCell header percent={30} weight={600} color={muted}>
{label}
</NswTableCell>
<NswTableCell>
{href === undefined ? (
value
) : (
<NswLink href={href} color={strong}>
{value}
</NswLink>
)}
</NswTableCell>
</NswTableRow>
))}
</NswTable>
)}
/><TablePage> is a demo helper shown inline above, not part of the package: copy it with the example, or swap in your own.
Basic table
A header row and striped rows in a bordered grid, in three colours.
39.5 KB built. Open the built email (opens in a new tab)
const themes = [
{ key: 'grey', strong: 'grey-800', muted: 'grey-700', line: 'grey-200', tint: 'grey-50' },
{
key: 'primary',
strong: 'primary-800',
muted: 'primary-700',
line: 'primary-200',
tint: 'primary-50',
},
{
key: 'accent',
strong: 'accent-800',
muted: 'accent-700',
line: 'accent-200',
tint: 'accent-50',
},
] as const
function TablePage({
title,
table,
}: {
title: string
table: (theme: (typeof themes)[number]) => ReactNode
}) {
return (
<NswLayout title={title}>
{themes.map((theme) => (
<Fragment key={theme.key}>
<NswColumns padding={false} background='white'>
<NswColumn>
<NswSpacer height={32} />
</NswColumn>
</NswColumns>
<NswColumns background='white'>
<NswColumn>{table(theme)}</NswColumn>
</NswColumns>
<NswColumns padding={false} background='white'>
<NswColumn>
<NswSpacer height={32} />
</NswColumn>
</NswColumns>
<NswColumns padding={false} background='grey-100'>
<NswColumn>
<NswSpacer height={24} />
</NswColumn>
</NswColumns>
</Fragment>
))}
</NswLayout>
)
}
const headers = ['Column 1', 'Column 2', 'Column 3']
const rows = [
['Data 1', 'Data 2', 'Data 3'],
['Data 4', 'Data 5', 'Data 6'],
['Data 7', 'Data 8', 'Data 9'],
];
<TablePage
title='Basic table'
table={({ strong, line, tint }) => (
<NswTable
type='body-sm'
lineHeight={16}
padding={12}
color={strong}
background='white'
stripeBackgroundColor={tint}
borders='all'
borderColor={line}
>
<NswTableRow head background={strong} color='white' weight={600}>
{headers.map((label) => (
<NswTableCell key={label}>{label}</NswTableCell>
))}
</NswTableRow>
{rows.map((row) => (
<NswTableRow key={row[0]}>
{row.map((cell) => (
<NswTableCell key={cell}>{cell}</NswTableCell>
))}
</NswTableRow>
))}
</NswTable>
)}
/><TablePage> is a demo helper shown inline above, not part of the package: copy it with the example, or swap in your own.
Schedule
A titled schedule: set column widths, and cells that hold stacked text.
90.5 KB built. Open the built email (opens in a new tab)
const themes = [
{ key: 'grey', strong: 'grey-800', muted: 'grey-700', line: 'grey-200', tint: 'grey-50' },
{
key: 'primary',
strong: 'primary-800',
muted: 'primary-700',
line: 'primary-200',
tint: 'primary-50',
},
{
key: 'accent',
strong: 'accent-800',
muted: 'accent-700',
line: 'accent-200',
tint: 'accent-50',
},
] as const
function TablePage({
title,
table,
}: {
title: string
table: (theme: (typeof themes)[number]) => ReactNode
}) {
return (
<NswLayout title={title}>
{themes.map((theme) => (
<Fragment key={theme.key}>
<NswColumns padding={false} background='white'>
<NswColumn>
<NswSpacer height={32} />
</NswColumn>
</NswColumns>
<NswColumns background='white'>
<NswColumn>{table(theme)}</NswColumn>
</NswColumns>
<NswColumns padding={false} background='white'>
<NswColumn>
<NswSpacer height={32} />
</NswColumn>
</NswColumns>
<NswColumns padding={false} background='grey-100'>
<NswColumn>
<NswSpacer height={24} />
</NswColumn>
</NswColumns>
</Fragment>
))}
</NswLayout>
)
}
const sessions: ReadonlyArray<{
time: string
heading: string
detail?: string
speakers: readonly string[]
striped?: boolean
}> = [
{
time: '9:00 AM',
heading: 'Opening Keynote: The Future of Digital Government',
detail: 'Exploring how technology transforms public service delivery',
speakers: ['Hon. Minister Smith', 'Minister for Digital Government'],
},
{
time: '10:30 AM',
heading: 'Panel: Citizen-Centric Design',
detail: 'Best practices for user experience in government services',
speakers: ['Sarah Johnson', 'Dr. Michael Chen', 'Lisa Williams'],
},
{ time: '12:00 PM', heading: 'Lunch Break & Networking', speakers: ['-'], striped: true },
{
time: '1:30 PM',
heading: 'Workshop: Data Analytics for Policy',
detail: 'Hands-on session on leveraging data for decision making',
speakers: ['Data Analytics Team', 'NSW Department of Customer Service'],
},
];
<TablePage
title='Schedule'
table={({ strong, muted, line, tint }) => (
<NswTable
type='body-sm'
lineHeight={16}
padding={12}
color={strong}
background='white'
borders='all'
borderColor={line}
>
<NswTableRow head background={strong} color='white' weight={600}>
{/* The legacy set no line height, and a client draws 16px type on a 19px line. */}
<NswTableCell colSpan={3} size={16} lineHeight={19}>
Conference Schedule - Day 1
</NswTableCell>
</NswTableRow>
<NswTableRow head background={strong} color='white' weight={600}>
<NswTableCell percent={20}>Time</NswTableCell>
<NswTableCell percent={50}>Session</NswTableCell>
<NswTableCell percent={30}>Speaker</NswTableCell>
</NswTableRow>
{sessions.map(({ time, heading, detail, speakers, striped }) => (
<NswTableRow key={time} background={striped ? tint : undefined}>
<NswTableCell weight={600} color={muted}>
{time}
</NswTableCell>
<NswTableCell>
<NswText type='body-sm' weight={700} color={strong}>
{heading}
</NswText>
{detail === undefined ? null : (
<>
<NswSpacer height={4} />
<NswText type='body-xs' color={muted}>
{detail}
</NswText>
</>
)}
</NswTableCell>
<NswTableCell>
{speakers.map((speaker, i) => (
<Fragment key={speaker}>
{i === 0 ? null : <NswSpacer height={16} />}
<NswText type='body-sm' color={i === speakers.length - 1 ? muted : strong}>
{speaker}
</NswText>
</Fragment>
))}
</NswTableCell>
</NswTableRow>
))}
</NswTable>
)}
/><TablePage> is a demo helper shown inline above, not part of the package: copy it with the example, or swap in your own.
Props
NswTable
| Prop | Type | Default | Description |
|---|---|---|---|
stripeBackgroundColor | ColorValue | none | Fill every second row after the header rows with this colour, unless the row sets its own background: a theme colour name or a hex literal, the same colour rule as background. Default none. |
frame | boolean | false | Draw a border around the whole table, in borderColor and borderWidth. Default false. |
width | number | the full width of the column it sits in | The table's width in whole px. Default the full width of the column it sits in. |
aria-label | string | — | The table's accessible name, read before its contents, when the text around it does not say what it holds. Written as aria-label; an empty one throws. |
defaults | TextDefaults | — | The email's text settings (<NswLayout text>), handed down by the layout to every text-bearing primitive beneath it; the block's own props win. The walk reaches the package's own primitives wherever they stand in the tree, in children and in props that hold elements, but it cannot see inside a component of your own: give such a component the same settings you gave the layout (text, checked by resolveTextDefaults) as a prop, and hand them to the primitives it renders. |
devices | Devices | — | Which devices show the table: left out, every device; desktop hides it on a phone; mobile shows it on a phone only. A phone-only table is hidden inline and from Windows Outlook with mso-hide and the <!--[if !mso]> comments around it (see src/lib/devices.ts), as <NswText devices> is. |
align | Align | left | Where the text sits across the cell: left, center or right. Default left. |
valign | Valign | top | Where the text sits down the cell, for a row whose cells are different heights: top, middle or bottom. Default top. |
padding | TablePadding | table.padding (8) | The space inside the cell, in whole px: one value for every side, [vertical, horizontal], or [top, right, bottom, left]. Default table.padding (8). |
background | ColorValue | none | The fill behind the cell: a theme colour name or a hex literal, or 'transparent' for none; anything else throws (see resolveColor). Default none. |
borders | TableBorders | none | Which sides of the cell draw a border: all, none, horizontal, vertical, or a list of sides such as ['bottom']. Borders between cells meet in one line. Default none. |
borderColor | ColorValue | grey-200 | The colour of the cell's borders: a theme colour name or a hex literal; 'transparent' throws (draw no border instead). Default grey-200. |
borderWidth | number | 1 | The width of the cell's borders, in whole px. Default 1. |
type | TypeScaleName | body | The step of the NSW type scale the text is drawn in: display-lg, display-md, display-sm, headline-lg, headline-md, headline-sm, body-lg, body, body-sm, body-xs, label or label-sm. Every other prop overrides one value of that step. Default body. |
size | number | the type step's | Font size in whole px, above 0. Default the type step's. |
weight | number | the type step's | Font weight, a multiple of 100 from 100 to 900. Default the type step's. |
lineHeight | number | the type step's | Line height in whole px (24), written in pixels because Windows Outlook ignores a unitless one. Default the type step's. |
letterSpacing | number | 'normal' | the type step's | Letter spacing in px (-0.5), or 'normal'. Default the type step's. |
color | ColorValue | the type step's, primary-800 for display, headline and label, grey-800 otherwise | The text colour: a theme colour name (grey-800) or a hex literal (#22272b); 'transparent' and anything else throws (see resolveColor). Default the type step's, primary-800 for display, headline and label, grey-800 otherwise. |
family | 'sans' | 'serif' | 'mono' | sans | The face: sans (Public Sans), serif (Merriweather) or mono (the platform monospace). Windows Outlook loads no web font and uses the first installed face in the family's stack (Arial, Georgia or Merriweather, Consolas or Roboto Mono). Default sans. |
fontStyle | 'normal' | 'italic' | normal | italic or normal. Default normal. |
NswTableRow
| Prop | Type | Default | Description |
|---|---|---|---|
head | boolean | false | Make the row a header row: it is drawn in the table's thead, and its cells are column headers (th scope="col") unless one says otherwise. Header rows come before the others. Default false. |
align | Align | left | Where the text sits across the cell: left, center or right. Default left. |
valign | Valign | top | Where the text sits down the cell, for a row whose cells are different heights: top, middle or bottom. Default top. |
padding | TablePadding | table.padding (8) | The space inside the cell, in whole px: one value for every side, [vertical, horizontal], or [top, right, bottom, left]. Default table.padding (8). |
background | ColorValue | none | The fill behind the cell: a theme colour name or a hex literal, or 'transparent' for none; anything else throws (see resolveColor). Default none. |
borders | TableBorders | none | Which sides of the cell draw a border: all, none, horizontal, vertical, or a list of sides such as ['bottom']. Borders between cells meet in one line. Default none. |
borderColor | ColorValue | grey-200 | The colour of the cell's borders: a theme colour name or a hex literal; 'transparent' throws (draw no border instead). Default grey-200. |
borderWidth | number | 1 | The width of the cell's borders, in whole px. Default 1. |
type | TypeScaleName | body | The step of the NSW type scale the text is drawn in: display-lg, display-md, display-sm, headline-lg, headline-md, headline-sm, body-lg, body, body-sm, body-xs, label or label-sm. Every other prop overrides one value of that step. Default body. |
size | number | the type step's | Font size in whole px, above 0. Default the type step's. |
weight | number | the type step's | Font weight, a multiple of 100 from 100 to 900. Default the type step's. |
lineHeight | number | the type step's | Line height in whole px (24), written in pixels because Windows Outlook ignores a unitless one. Default the type step's. |
letterSpacing | number | 'normal' | the type step's | Letter spacing in px (-0.5), or 'normal'. Default the type step's. |
color | ColorValue | the type step's, primary-800 for display, headline and label, grey-800 otherwise | The text colour: a theme colour name (grey-800) or a hex literal (#22272b); 'transparent' and anything else throws (see resolveColor). Default the type step's, primary-800 for display, headline and label, grey-800 otherwise. |
family | 'sans' | 'serif' | 'mono' | sans | The face: sans (Public Sans), serif (Merriweather) or mono (the platform monospace). Windows Outlook loads no web font and uses the first installed face in the family's stack (Arial, Georgia or Merriweather, Consolas or Roboto Mono). Default sans. |
fontStyle | 'normal' | 'italic' | normal | italic or normal. Default normal. |
NswTableCell
| Prop | Type | Default | Description |
|---|---|---|---|
header | boolean | true in a header row (head), false elsewhere | Make the cell a header (th), which a screen reader reads the other cells by. Default true in a header row (head), false elsewhere. |
scope | 'col' | 'row' | 'colgroup' | 'rowgroup' | col in a header row (colgroup when the cell spans columns), row elsewhere (rowgroup when it spans rows) | What the header cell labels: col, row, colgroup or rowgroup. Giving it makes the cell a header. Default col in a header row (colgroup when the cell spans columns), row elsewhere (rowgroup when it spans rows). |
colSpan | number | 1 | How many columns the cell spans, a whole number from 1. Default 1. |
rowSpan | number | 1 | How many rows the cell spans, a whole number from 1. Default 1. |
percent | number | a share by content, as every client sizes a table | The share of the table's width the cell's column takes, a percentage above 0 and at most 100. Give this or width, not both. Default a share by content, as every client sizes a table. |
width | number | — | The width of the cell's column in whole px. Give this or percent, not both. |
align | Align | left | Where the text sits across the cell: left, center or right. Default left. |
valign | Valign | top | Where the text sits down the cell, for a row whose cells are different heights: top, middle or bottom. Default top. |
padding | TablePadding | table.padding (8) | The space inside the cell, in whole px: one value for every side, [vertical, horizontal], or [top, right, bottom, left]. Default table.padding (8). |
background | ColorValue | none | The fill behind the cell: a theme colour name or a hex literal, or 'transparent' for none; anything else throws (see resolveColor). Default none. |
borders | TableBorders | none | Which sides of the cell draw a border: all, none, horizontal, vertical, or a list of sides such as ['bottom']. Borders between cells meet in one line. Default none. |
borderColor | ColorValue | grey-200 | The colour of the cell's borders: a theme colour name or a hex literal; 'transparent' throws (draw no border instead). Default grey-200. |
borderWidth | number | 1 | The width of the cell's borders, in whole px. Default 1. |
type | TypeScaleName | body | The step of the NSW type scale the text is drawn in: display-lg, display-md, display-sm, headline-lg, headline-md, headline-sm, body-lg, body, body-sm, body-xs, label or label-sm. Every other prop overrides one value of that step. Default body. |
size | number | the type step's | Font size in whole px, above 0. Default the type step's. |
weight | number | the type step's | Font weight, a multiple of 100 from 100 to 900. Default the type step's. |
lineHeight | number | the type step's | Line height in whole px (24), written in pixels because Windows Outlook ignores a unitless one. Default the type step's. |
letterSpacing | number | 'normal' | the type step's | Letter spacing in px (-0.5), or 'normal'. Default the type step's. |
color | ColorValue | the type step's, primary-800 for display, headline and label, grey-800 otherwise | The text colour: a theme colour name (grey-800) or a hex literal (#22272b); 'transparent' and anything else throws (see resolveColor). Default the type step's, primary-800 for display, headline and label, grey-800 otherwise. |
family | 'sans' | 'serif' | 'mono' | sans | The face: sans (Public Sans), serif (Merriweather) or mono (the platform monospace). Windows Outlook loads no web font and uses the first installed face in the family's stack (Arial, Georgia or Merriweather, Consolas or Roboto Mono). Default sans. |
fontStyle | 'normal' | 'italic' | normal | italic or normal. Default normal. |