Rendering Tables
This guide covers different ways to render tables in MDX, including GFM syntax, literal HTML tag, and dynamic JavaScript expressions.
GFM syntax
In Markdown, it is preferable to write tables via GFM syntax.
| left | center | right |
| :----- | :----: | ----: |
| foo | bar | baz |
| banana | apple | kiwi |
will be rendered as:
left | center | right |
---|---|---|
foo | bar | baz |
banana | apple | kiwi |
HTML Literal Tables
If you try to render the following literal <table />
element:
<table>
<thead>
<tr>
<th>left</th>
<th align="center">center</th>
<th align="right">right</th>
</tr>
</thead>
<tbody>
<tr>
<td>foo</td>
<td align="center">bar</td>
<td align="right">baz</td>
</tr>
<tr>
<td>banana</td>
<td align="center">apple</td>
<td align="right">kiwi</td>
</tr>
</tbody>
</table>
you’ll get the following result:
left | center | right |
---|---|---|
foo | bar | baz |
banana | apple | kiwi |
Confused by unstyled elements? We explained here, why this happens.
Dynamic Tables
How to Write
Want to render a dynamic table? You can use embedded JavaScript expressions into your table for it:
<table>
<thead>
<tr>
<th>Country</th>
<th>Flag</th>
</tr>
</thead>
<tbody>
{[
{ country: 'France', flag: '🇫🇷' },
{ country: 'Ukraine', flag: '🇺🇦' }
].map(item => (
<tr key={item.country}>
<td>{item.country}</td>
<td>{item.flag}</td>
</tr>
))}
</tbody>
</table>
will be rendered as:
Country | Flag |
---|---|
France | 🇫🇷 |
Ukraine | 🇺🇦 |
Confused by unstyled elements? We explain below 👇 why it happens.
Unexpected Result
Table looks different compared to GFM syntax table:
- only children of table body
<tbody />
is styled - table header is unstyled
- table doesn’t have margin top
Why This Happens
MDX doesn’t replace literal HTML
elements with <MDXProvider />
.
Adam Wathan, creator of Tailwind CSS submitted an issue in MDX2 to have some an escape hatch that we can name like:
please only transform markdown tags, not literal HTML tags
Table header looks unstyled since it has not been replaced with Nextra’s MDX
components <tr />
, <th />
and <td />
, for the same
reason <table />
literal is not replaced and doesn’t have default
margin-top aka mt-6
.
Ways to Fix It
One-Time Fix
Just wrap your table with curly braces {
and }
, e.g.
{<table>
...
</table>}
Alternatively, create a reusable table component:
export function Table({ columns, data }) {
return <table>{/* ... */}</table>
}
<Table columns={...} data={...} />
Changing Default Behaviour
If this thing is still confusing for you, and you want to use regular literal HTML elements for your tables, do the following:
Install remark-mdx-disable-explicit-jsx
package
npm i remark-mdx-disable-explicit-jsx
Setup
Configure plugin in nextra
function inside next.config.mjs
file
import nextra from 'nextra'
import remarkMdxDisableExplicitJsx from 'remark-mdx-disable-explicit-jsx'
const withNextra = nextra({
theme: 'nextra-theme-docs',
themeConfig: './theme.config.tsx',
mdxOptions: {
remarkPlugins: [
[
remarkMdxDisableExplicitJsx,
{ whiteList: ['table', 'thead', 'tbody', 'tr', 'th', 'td'] }
]
]
}
})
export default withNextra()