How to Make a Table in a Jupyter Notebook Markdown Cell
By MarkdownTables.com editorial teamUpdated
On this page
How do I make a table in a Jupyter Markdown cell?
- Switch the cell to Markdown. Select the cell in command mode and press M, or use the cell type menu in the toolbar.
- Write the header row with cells separated by pipes, then a delimiter row with one cell per column (
:---left,:---:center,---:right), then the data rows. - Leave a blank line above the table if there is text before it.
- Run the cell with Shift+Enter. A Markdown cell shows its source until you run it.
| Step | Cell type | Run with |
| :--- | :-------- | :------- |
| Load data | Code | Shift+Enter |
| Explain the model | Markdown | Shift+Enter |
| Plot results | Code | Shift+Enter || Step | Cell type | Run with |
|---|---|---|
| Load data | Code | Shift+Enter |
| Explain the model | Markdown | Shift+Enter |
| Plot results | Code | Shift+Enter |
The pipes don’t need to line up. The full rules are in the Markdown table syntax guide.
Open in editor : A Markdown cell with a pipe tableJupyter renders GitHub-style pipe tables, including inline formatting (**bold**, `code`, links) and $…$ math inside cells. Anything block-level, such as lists, fenced code or several paragraphs, needs an HTML table instead.
Four ways to get a table into a notebook
| Method | Use it when | Merged cells | Alignment control |
|---|---|---|---|
| Markdown cell, pipe table | Static, hand-written tables | ❌ No | Colons in the delimiter row |
| Markdown cell, HTML table | Merged cells, lists or links with attributes | ✅ colspan, rowspan | align attribute or CSS |
| DataFrame as the last expression | Data you already have in pandas | ✅ MultiIndex headers | df.style and CSS |
Markdown(df.to_markdown()) | A Markdown table that also exports to text | ❌ No | colalign= and colons |
itables or similar | Large tables that readers sort and search | ❌ Not by default | Library options |
For generating tables from code, the Python Markdown tables guide covers tabulate, to_markdown() options and parsing.
Why is my Jupyter Markdown table right-aligned, and how do I align it left?
Jupyter doesn't render Markdown into bare HTML: it adds its own table stylesheet. What is commonly reported, and what you can check in your browser's inspector:
- The classic Notebook styles table cells with
text-align: rightand centers the table on the page, a look designed for DataFrame output. Plain Markdown tables inherit it. - JupyterLab also centers tables and applies its own cell styles. Reports on how headers and cells align differ between versions, so verify on yours.
- Alignment colons set the cell's
alignattribute (or an inline style, depending on the renderer version). A stylesheet rule beats the attribute, which is why colons sometimes appear to do nothing.
Work down this list until the table aligns:
- Put an explicit colon in every column:
:---for text,---:for numbers. See column alignment. - Add a
%%htmlcell with a<style>block that setstext-align: leftand removes the automatic margins. Jupyter versions use different class names, so the rule below lists both:
%%html
<style>
.rendered_html table, .jp-RenderedHTMLCommon table { margin-left: 0; }
.rendered_html th, .rendered_html td,
.jp-RenderedHTMLCommon th, .jp-RenderedHTMLCommon td { text-align: left; }
</style>- For a permanent change, put the same rules in
custom.css(see the Jupyter section of the alignment guide for where it lives and how to enable it in JupyterLab). - For DataFrames, style the table object rather than the page:
import pandas as pd
df = pd.DataFrame({"Name": ["Ada", "Grace"], "Role": ["Engineer", "Admiral"]})
styles = [
{"selector": "th", "props": [("text-align", "left")]},
{"selector": "td", "props": [("text-align", "left")]},
]
df.style.hide(axis="index").set_table_styles(styles)Style blocks can be removed in notebooks that aren't trusted, and in other viewers such as GitHub. Treat CSS as a nicety for your own screen, not as part of the content.
Can I merge cells in a Jupyter table?
Markdown pipe tables have no merged cells, so you have two options. Write an HTML table in the Markdown cell, with colspan and rowspan; Jupyter renders raw HTML in Markdown cells:
<table>
<thead>
<tr>
<th rowspan="2">Model</th>
<th colspan="2">Accuracy</th>
</tr>
<tr>
<th>Train</th>
<th>Test</th>
</tr>
</thead>
<tbody>
<tr><td>Baseline</td><td>0.91</td><td>0.84</td></tr>
<tr><td>Tuned</td><td>0.93</td><td>0.88</td></tr>
</tbody>
</table>Or let pandas do it. A DataFrame with MultiIndex columns or rows displays the repeated labels as merged cells without any HTML from you:
import pandas as pd
df = pd.DataFrame(
{("Accuracy", "Train"): [0.91, 0.93], ("Accuracy", "Test"): [0.84, 0.88]},
index=["Baseline", "Tuned"],
)
df # the "Accuracy" header spans both columnsRemember the notebook is the audience for the HTML table. It can be sanitized or lose styling on GitHub, in some exports and in other viewers. See merged cells in Markdown tables for the broader picture.
Math and LaTeX in a notebook table
Jupyter typesets $…$ with MathJax, including inside table cells. The one rule: don't write a literal | inside the math, because the table parser splits on it first. Use \mid, \vert or \lVert … \rVert instead.
| Metric | Formula |
| :-------- | :------------------------------- |
| Precision | $\frac{TP}{TP + FP}$ |
| Recall | $\frac{TP}{TP + FN}$ |
| Norm | $\lVert x \rVert_2$ |
| Odds | $P(A \mid B)$ || Metric | Formula |
|---|---|
| Precision | $\frac{TP}{TP + FP}$ |
| Recall | $\frac{TP}{TP + FN}$ |
| Norm | $\lVert x \rVert_2$ |
| Odds | $P(A \mid B)$ |
The preview here shows the math source. In Jupyter, MathJax typesets it.
Open in editor : Inline math in a tableA real LaTeX tabular won't render. MathJax handles math mode only, so \begin{tabular} shows as text. Use a Markdown table, or a math-mode \begin{array}. Both are covered in LaTeX tables in Jupyter. To produce tabular source for a paper, use Markdown table to LaTeX or df.to_latex().
DataFrames: display, to_markdown() and Markdown()
A DataFrame that is the last expression of a code cell (or the argument of display()) is shown as an HTML table by pandas, not as Markdown. That table has Jupyter's DataFrame styling, which is why it can look different from a Markdown table. When you want a Markdown table, build it explicitly:
import pandas as pd
from IPython.display import Markdown, display
df = pd.DataFrame({"Model": ["Baseline", "Tuned"], "Accuracy": [0.81, 0.89]})
# Renders as a table in the output area (needs the tabulate package)
display(Markdown(df.to_markdown(index=False, colalign=("left", "right"))))
# Prints the Markdown source, useful for copying into a README
print(df.to_markdown(index=False))df.to_markdown()needs thetabulatepackage (pip install tabulate).Markdown(...)only wraps the string;display()sends it to the notebook to be rendered. Withoutdisplay()it renders only when it is the last expression of the cell.print(df.to_markdown())gives plain text, which is what you want when you will paste the table into a README, wiki or issue.- Pass
index=Falseto drop the row index, andcolalign=orfloatfmt=to control alignment and number formats; they are handed totabulate.
For a table built from your own rows, an f-string is enough:
from IPython.display import Markdown, display
rows = [("Baseline", 0.81), ("Tuned", 0.89)]
lines = ["| Model | Accuracy |", "| :--- | ---: |"]
lines += [f"| {name} | {acc:.2f} |" for name, acc in rows]
display(Markdown("\n".join(lines)))Large DataFrames are better served by an interactive table. A library such as itables adds sorting and search in the output area:
from itables import show
show(df) # sortable, searchable table in the output areaThat interactivity needs JavaScript, so it doesn't survive in static viewers such as GitHub's notebook preview.
JupyterLab, VS Code, Colab and GitHub differ
The same .ipynb is rendered by different software depending on where you open it, so the same cell can look different:
| Environment | Pipe tables | HTML tables in cells | Alignment colons |
|---|---|---|---|
| JupyterLab / Notebook 7Rendered by the JupyterLab Markdown renderer | Yes | Yes: Raw HTML in cells works in a trusted notebook | Partly: Jupyter’s table CSS can override colons |
| Classic Notebook (6.x) | Yes | Yes: Trusted notebooks | Partly: Commonly reported: cells right-aligned by CSS |
| VS Code notebooksUses VS Code’s own Markdown renderer | Yes | Partly: HTML is sanitized; <style> may be stripped | Yes: Colons usually respected |
| Google Colab | Yes | Partly: Basic table HTML; check scripts and styles | Partly: Check in your notebook |
| GitHub (.ipynb viewer)GitHub renders the notebook with its own Markdown | Yes | Partly: Allowlisted tags; style removed | Yes: Colons respected |
| nbconvert HTML export | Yes | Yes: HTML kept as written | Partly: Follows the exported template’s CSS |
✅ supported · ⚠️ partly or with a workaround · ❌ not supported · Last verified October 2026
Where a cell says “check”, the behavior depends on version or settings. Open one test table in each environment you care about.
VS Code
VS Code renders notebook Markdown with its own Markdown engine, not with Jupyter's stylesheet, so the right-alignment quirk generally doesn't apply there. Its HTML sanitization is stricter than the classic Notebook's, so keep structural HTML only. More on previews in the VS Code tables guide.
Google Colab
Colab text cells accept Markdown tables and $…$ math. Colab also offers an interactive-table view for DataFrames, turned on with %load_ext google.colab.data_table and off with %unload_ext google.colab.data_table. Colab's Markdown cells may strip more HTML than a local Jupyter does, so test an HTML table before relying on it.
On GitHub
GitHub renders notebooks with its own Markdown, so your custom CSS and %%html style cells have no effect there, and colons are respected. Table basics are in the GitHub tables guide.
Exporting with nbconvert: HTML, PDF and Markdown
nbconvert exports the notebook through different engines, and tables fare differently:
jupyter nbconvert --to html analysis.ipynb
jupyter nbconvert --to markdown analysis.ipynb
jupyter nbconvert --to pdf analysis.ipynb # needs pandoc and a LaTeX install
jupyter nbconvert --to webpdf analysis.ipynb # renders through a headless browser- HTML keeps pipe tables and HTML tables as rendered in the notebook, using the export template's CSS.
- Markdown passes Markdown cells through unchanged. DataFrame outputs come out as HTML blocks in the
.mdfile, so useto_markdown()for Markdown output you want to publish. - PDF via LaTeX converts Markdown cells with pandoc. Pipe tables convert; raw HTML tables are generally not carried over. DataFrames typically fall back to their plain-text form, so check the result.
- Web PDF prints the rendered HTML through a headless browser and keeps styled tables.
If you work in Quarto, notebooks render with Quarto's own table options; see Quarto and R Markdown tables. For Pandoc-level control over captions and widths, see Pandoc Markdown tables.
Common notebook table mistakes
No blank line before the table
Text directly above the header row can swallow the table into the paragraph, so it shows as plain text with pipes.
Results from the last run:
| Model | Accuracy |
| ----- | -------- |
| Tuned | 0.89 || Model | Accuracy |
|---|---|
| Tuned | 0.89 |
Some parsers still render this; others don’t. A blank line always works.
Results from the last run:
| Model | Accuracy |
| ----- | -------- |
| Tuned | 0.89 || Model | Accuracy |
|---|---|
| Tuned | 0.89 |
Other mistakes
- Forgetting to run the cell. An edited Markdown cell shows its raw source until you run it.
- Header and delimiter rows with different cell counts render as plain text. Check them with the Markdown table validator.
- Pipes in code or math split cells. Escape as
\|in code and use\midin math. See escaping pipes. - Relying on CSS or scripts in an untrusted notebook. Jupyter removes them until the notebook is trusted.
- Typing wide tables by hand. Paste from a spreadsheet instead with CSV to Markdown table or build one in the table generator.
Frequently asked questions
How do I make a table in a Jupyter Notebook?
Change a cell to Markdown, then write a header row with pipes, a delimiter row such as | --- | --- |, and one line per row. Run the cell with Shift+Enter to render it. For a table built from data, show a pandas DataFrame or pass df.to_markdown() to Markdown().
Why is my Jupyter Markdown table right-aligned?
Jupyter’s own table stylesheet is the usual cause: the classic Notebook is commonly reported to right-align cells, a style meant for DataFrame output. Add colons such as :--- to the delimiter row first. If that doesn’t change it, override the CSS with text-align: left in a %%html cell or custom.css.
Can you merge cells in a Jupyter Markdown table?
Not with pipe syntax. Write the table in HTML inside the Markdown cell and use colspan and rowspan. For DataFrames, give the columns or index a MultiIndex and pandas draws the merged headers for you.
How do I put a LaTeX table in a Jupyter Markdown cell?
Use a Markdown pipe table and put math in $…$. Jupyter’s MathJax typesets math only, so \begin{tabular} shows as text. For a ruled grid in math mode use \begin{array}; see math and LaTeX in tables.
How do I display a pandas DataFrame as a Markdown table in Jupyter?
Run pip install tabulate, then display(Markdown(df.to_markdown(index=False))) after from IPython.display import Markdown, display. Printing df.to_markdown() shows the Markdown source instead of rendering it.
Does a Jupyter Markdown table need a blank line before it?
Yes, leave one. Some Markdown parsers need a blank line before a table, and a blank line avoids the lines being read as part of the paragraph above. Also keep the header and delimiter rows at the same number of cells.
Why does my notebook table disappear in the PDF export?
Raw HTML tables in Markdown cells are generally not carried into a LaTeX PDF, because that path converts through pandoc. Use a pipe table in the cell, or export with jupyter nbconvert --to webpdf, which prints the rendered page and keeps the HTML.
Draft the table outside the notebook
Edit cells in a grid, set alignment per column, and paste the Markdown into a cell.