A NSW Government website

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

Props of NswTable, read from the component
PropTypeDefaultDescription
stripeBackgroundColorColorValuenoneFill 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.
framebooleanfalseDraw a border around the whole table, in borderColor and borderWidth. Default false.
widthnumberthe full width of the column it sits inThe table's width in whole px. Default the full width of the column it sits in.
aria-labelstring—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.
defaultsTextDefaults—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.
devicesDevices—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.
alignAlignleftWhere the text sits across the cell: left, center or right. Default left.
valignValigntopWhere the text sits down the cell, for a row whose cells are different heights: top, middle or bottom. Default top.
paddingTablePaddingtable.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).
backgroundColorValuenoneThe fill behind the cell: a theme colour name or a hex literal, or 'transparent' for none; anything else throws (see resolveColor). Default none.
bordersTableBordersnoneWhich 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.
borderColorColorValuegrey-200The colour of the cell's borders: a theme colour name or a hex literal; 'transparent' throws (draw no border instead). Default grey-200.
borderWidthnumber1The width of the cell's borders, in whole px. Default 1.
typeTypeScaleNamebodyThe 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.
sizenumberthe type step'sFont size in whole px, above 0. Default the type step's.
weightnumberthe type step'sFont weight, a multiple of 100 from 100 to 900. Default the type step's.
lineHeightnumberthe type step'sLine height in whole px (24), written in pixels because Windows Outlook ignores a unitless one. Default the type step's.
letterSpacingnumber | 'normal'the type step'sLetter spacing in px (-0.5), or 'normal'. Default the type step's.
colorColorValuethe type step's, primary-800 for display, headline and label, grey-800 otherwiseThe 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'sansThe 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'normalitalic or normal. Default normal.

NswTableRow

Props of NswTableRow, read from the component
PropTypeDefaultDescription
headbooleanfalseMake 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.
alignAlignleftWhere the text sits across the cell: left, center or right. Default left.
valignValigntopWhere the text sits down the cell, for a row whose cells are different heights: top, middle or bottom. Default top.
paddingTablePaddingtable.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).
backgroundColorValuenoneThe fill behind the cell: a theme colour name or a hex literal, or 'transparent' for none; anything else throws (see resolveColor). Default none.
bordersTableBordersnoneWhich 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.
borderColorColorValuegrey-200The colour of the cell's borders: a theme colour name or a hex literal; 'transparent' throws (draw no border instead). Default grey-200.
borderWidthnumber1The width of the cell's borders, in whole px. Default 1.
typeTypeScaleNamebodyThe 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.
sizenumberthe type step'sFont size in whole px, above 0. Default the type step's.
weightnumberthe type step'sFont weight, a multiple of 100 from 100 to 900. Default the type step's.
lineHeightnumberthe type step'sLine height in whole px (24), written in pixels because Windows Outlook ignores a unitless one. Default the type step's.
letterSpacingnumber | 'normal'the type step'sLetter spacing in px (-0.5), or 'normal'. Default the type step's.
colorColorValuethe type step's, primary-800 for display, headline and label, grey-800 otherwiseThe 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'sansThe 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'normalitalic or normal. Default normal.

NswTableCell

Props of NswTableCell, read from the component
PropTypeDefaultDescription
headerbooleantrue in a header row (head), false elsewhereMake 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).
colSpannumber1How many columns the cell spans, a whole number from 1. Default 1.
rowSpannumber1How many rows the cell spans, a whole number from 1. Default 1.
percentnumbera share by content, as every client sizes a tableThe 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.
widthnumber—The width of the cell's column in whole px. Give this or percent, not both.
alignAlignleftWhere the text sits across the cell: left, center or right. Default left.
valignValigntopWhere the text sits down the cell, for a row whose cells are different heights: top, middle or bottom. Default top.
paddingTablePaddingtable.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).
backgroundColorValuenoneThe fill behind the cell: a theme colour name or a hex literal, or 'transparent' for none; anything else throws (see resolveColor). Default none.
bordersTableBordersnoneWhich 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.
borderColorColorValuegrey-200The colour of the cell's borders: a theme colour name or a hex literal; 'transparent' throws (draw no border instead). Default grey-200.
borderWidthnumber1The width of the cell's borders, in whole px. Default 1.
typeTypeScaleNamebodyThe 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.
sizenumberthe type step'sFont size in whole px, above 0. Default the type step's.
weightnumberthe type step'sFont weight, a multiple of 100 from 100 to 900. Default the type step's.
lineHeightnumberthe type step'sLine height in whole px (24), written in pixels because Windows Outlook ignores a unitless one. Default the type step's.
letterSpacingnumber | 'normal'the type step'sLetter spacing in px (-0.5), or 'normal'. Default the type step's.
colorColorValuethe type step's, primary-800 for display, headline and label, grey-800 otherwiseThe 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'sansThe 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'normalitalic or normal. Default normal.