# Rendering a table

Draw an aligned table into a message with renderTable(). Covers picking a frame, aligning and capping columns, wide characters and emoji, filling and spacing, and paging past Discord's character limit.

Discord doesn't render table markup, so a leaderboard or a settings dump comes out as a ragged list. [`renderTable`](https://docs.seedcord.org/packages/utils/latest/render-table) draws aligned columns into a string you can send.

```ts
const table = renderTable([
    ['Command', 'Uses'],
    ['/ban', '412'],
    ['/kick', '87'],
    ['/award', '1203']
]);
```

```txt output
╭─────────┬──────╮
│ Command │ Uses │
├─────────┼──────┤
│ /ban    │ 412  │
├─────────┼──────┤
│ /kick   │ 87   │
├─────────┼──────┤
│ /award  │ 1203 │
╰─────────┴──────╯
```

The first argument is rows of strings, and row zero becomes the header. Each column grows to fit its widest cell.

> **Tip**
>
> Discord shows a message in a proportional font, so the columns don't line up. Pass `fence: true` to wrap the output in a code block.

## Picking a frame

The frame decides where the table can go. A box frame reads well in a Discord code block, and `markdown` suits a file or a gist. Every table so far used the default frame, and `border` picks another one.

```ts
renderTable(rows, { border: 'rounded' }); // the default
renderTable(rows, { border: 'double' });
renderTable(rows, { border: 'ascii' });
renderTable(rows, { border: 'markdown' });
```

`double` draws a heavier frame. It's the only style that gives the header its own separator glyphs. `ascii` draws with `+`, `-`, and `|`.

```txt output
double            ascii

╔═══════╦═════╗   +-------+-----+
║ Name  ║ Age ║   | Name  | Age |
╠═══════╬═════╣   +-------+-----+
║ Alice ║ 30  ║   | Alice | 30  |
╚═══════╩═════╝   +-------+-----+
```

`markdown` drops the outer frame and emits a GitHub-flavored table, the kind with a `| --- |` delimiter row.

```txt output
| Name  | Age |
| --- | --- |
| Alice | 30  |
```

> **Warning**
>
> Discord doesn't render GitHub-flavored tables in a message. Use `markdown` for output headed somewhere else, a gist or a repo file. Take a box border with `fence: true` for a Discord message.

## Aligning columns

Pass `align` one value to set every column, or an array to set them one at a time. Any column the array doesn't cover falls back to left.

```ts
renderTable(rows, { align: 'center' });
renderTable(rows, { align: ['left', 'right', 'center'] });
```

`numericAlign` right-aligns any column whose body cells all read as numbers, which lines the digits up on a scoreboard. An explicit `align` for that column wins.

```ts
renderTable(
    [
        ['Player', 'Score'],
        ['ava', '100'],
        ['ben', '9'],
        ['cy', '42']
    ],
    { numericAlign: true }
);
```

```txt output
╭────────┬───────╮
│ Player │ Score │
├────────┼───────┤
│ ava    │   100 │
├────────┼───────┤
│ ben    │     9 │
├────────┼───────┤
│ cy     │    42 │
╰────────┴───────╯
```

The test skips an empty cell. A cell holding one space passes it, because `Number(' ')` is `0`. If you set `emptyCell` to something like `'-'`, the test reads a `'-'` in every empty cell, which makes the column non-numeric.

## Capping a column

A column grows to fit its widest cell, so one long description stretches every row of the table with it. `maxWidth` sets the widest a column can get, counted in display columns. `overflow` picks what happens to a cell over that cap.

```ts
renderTable(rows, { maxWidth: 20 }); // wraps, the default
renderTable(rows, { maxWidth: 20, overflow: 'truncate' });
```

`wrap` breaks the cell across several lines inside its row. `truncate` cuts it and adds a trailing ellipsis, which counts toward the cap.

```txt output
wrap             truncate

╭────────────╮   ╭────────────╮
│ hello      │   │ hello wor… │
│ world this │   ╰────────────╯
│ is long    │
╰────────────╯
```

A markdown row holds one line, so that border truncates whatever `overflow` says.

> **Warning**
>
> `maxWidth` throws a `RangeError` on zero, a negative, or a fraction. `padding` throws on a negative or a fraction. Both checks run before any drawing.

## Wide characters and emoji

Latin letters, digits, and punctuation line up everywhere. Emoji break the frame in Discord. CJK comes close enough to read.

`renderTable` counts a CJK character and an emoji as two columns, then pads on that count. A terminal whose font covers those characters draws the result correctly.

```ts
renderTable([
    ['Name', 'Label'],
    ['Tokyo', '東京'],
    ['Party', '🎉🎉']
]);
```

```txt output
╭───────┬───────╮
│ Name  │ Label │
├───────┼───────┤
│ Tokyo │ 東京  │
├───────┼───────┤
│ Party │ 🎉🎉  │
╰───────┴───────╯
```

That block is the terminal render. Discord draws an emoji as an image sized to the line height, which is wider than two characters. Discord then draws that row's right border further right. Padding can't correct it, since the count is in columns and the client is placing pictures.

Put emoji in a line above or below the table. Keep the columns to text.

Leave trimming to `maxWidth`. Your own trim counts characters differently, so the widths come out wrong.

## Filling and spacing

{/* prettier-ignore-start */}

| option      | type      | holds                                                |
| ----------- | --------- | ---------------------------------------------------- |
| `header`    | `boolean` | treats row 0 as a header, `true` by default          |
| `padding`   | `number`  | spaces on each side of every cell, `1` by default    |
| `emptyCell` | `string`  | fills a missing or empty cell, `''` by default       |
| `fence`     | `boolean` | wraps the output in a code block, `false` by default |

{/* prettier-ignore-end */}

Your rows can differ in length. The longest row sets the column count, so `emptyCell` fills the gaps in every shorter row.

A newline inside a cell becomes a space, which keeps the frame from splitting across lines. Multi-line cells come from `wrap`.

## Paging past the character limit

Discord caps a message at 2000 characters. Passing `budget` returns an array of pages, each one carrying its own header.

```ts
const pages = renderTable(rows, { budget: 2000, fence: true });
```

You get the array whenever the options object has a `budget` key, whatever its value. Passing `budget: undefined` still returns an array.

`budget` is a target. A single row longer than the whole budget still gets a page of its own, since a page smaller than one row cannot exist. With `fence` on, each page is measured with its fence included, so every page still fits.

> **Note**
>
> This paging splits a string by length. [Pagination](/components/pagination) is the separate button-driven system for a message someone clicks through. Feed these pages into a paginator to get both.
