npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ts-tableau

v0.3.0

Published

A library that simplifies table layout by separating data from layout, for use as a preprocessor for Markdown and similar markup languages.

Readme

Tableau: Enhanced Markdown Tables

Tableau separates a table's data from its layout. You write the rows as plain pipe-separated text, then, after a line of ===, say how they should look: headers, spans, rules, alignment, shading, captions. It is intended as a preprocessor for Markdown and similar markup languages, but can be used standalone to generate HTML.

Try it in the playground, or read the guide.

Examples

A header, a footer, and rules between the rows:

Name  | Score | Grade
Alice | 92    | A
Bob   | 81    | B
Carol | 74    | C
Average | 82  |
===
[r1] header
[r$r] footer
hlines

Selectors can be dynamic. $tr is the row being styled, so c$tr picks out the diagonal, and c2-$tr~1 everything to its left:

|  x |  1 |  2 |  3 |  4 |  5 |
|  1 |  1 |  2 |  3 |  4 |  5 |
|  2 |  2 |  4 |  6 |  8 | 10 |
|  3 |  3 |  6 |  9 | 12 | 15 |
|  4 |  4 |  8 | 12 | 16 | 20 |
|  5 |  5 | 10 | 15 | 20 | 25 |
===
# Times table
[r1;c1] header

-- $tr is the row being matched, so c$tr is the diagonal
[r2-$r:c$tr] line(lb)

-- everything left of the diagonal: the repeated products
[r2-$r:c2-$tr~1] small bg(shade6)

Spans across columns and down rows, for a conference programme:

|       | Main Hall | Room A | Room B
| 09:00 | Keynote: _The Shape of Data_ | |
| 10:00 | Tables Are Interfaces | Parsing Pipes | Workshop: Build a Renderer
| 11:00 | Spans in Practice | Layout Algebra |
| 12:00 | Lunch | |
| 13:00 | Lightning Talks | Accessible Tables | Print & PDF
| 14:00 | Panel: Is Markdown Enough? | | Hallway Track
===
# DataDocs Conf · Day 1
align(cm)
[r1;c1] header

-- one session across every room
[r2:c2-$c] span bg(shade4)
[r5:c2-$c] span bg(shade1)

-- the workshop runs for two slots
[r3-4:c4] span bg(shade7)

[r7:c2-3] span

Two-level column headings, right-aligned figures, and rules where an accountant would put them, down to the double rule under the bottom line:

| $ thousands       | 2025 |     | 2026 (forecast) |
|                   | H1   | H2  | H1  | H2
| Product revenue   | 412  | 455 | 498 | 531
| Services          | 128  | 134 | 141 | 150
| **Total revenue** | 540  | 589 | 639 | 681
| Cost of sales     | 210  | 224 | 238 | 249
| Operating costs   | 190  | 197 | 205 | 214
| **Operating profit** | 140 | 168 | 196 | 218
===
# Half-yearly results
[r1-2] header
[r1-2:c1] span
[r1:c2-3;r1:c4-5] span

[c1] align(l)
[r2-$r:c2-$c] align(r)
[r3-$r:c4-5] fg(shade8)

-- rules above the subtotals, and a double rule under the bottom line
[r5;r$r] line(t)
[r$r] line(b, double, xthick)

Group headings that span the whole row, and a shaded column to draw the eye:

|                       | Free   | Pro       | Team
| Price per month       | €0     | €8        | €20 / seat
| **Storage**           |        |           |
| Projects              | 3      | Unlimited | Unlimited
| File size limit       | 10 MB  | 1 GB      | 5 GB
| Version history       | 7 days | 1 year    | Forever
| **Collaboration**     |        |           |
| Shared workspaces     | ✗      | ✓         | ✓
| Comments              | ✗      | ✓         | ✓
| Admin controls        | ✗      | ✗         | ✓
| **Support**           |        |           |
| Community forum       | ✓      | ✓         | ✓
| Email support         | ✗      | ✓         | ✓
| Account manager       | ✗      | ✗         | ✓
===
# Choose a plan
[r1] header
[c1] align(l)
[c2-$c] align(c)
[r2] line(b)

-- the plan we want you to pick
[r2-$r:c3] bg(shade3)

-- group headings span the whole row
[r3;r7;r11] span bg(shade5) align(l)

An APA-style results table: grouped headings, and rules only where they mean something:

| Logistic parameter                  | 9-year-olds| | 16-year-olds| | t(40) | _p_ | _d_
|                                     |   M  |  SD   |   M    | SD   |
Maximum asymptote, proportion         | .843 | .135  | .877   | .082 | 0.951 | .347 | 0.302
Crossover, in ms                      | 759  |   87  |  694   |   42 | 2.877 | .006 | 0.840
Slope, as change in proportion per ms | .001 | .0002 | .002   |.0002 | 2.635 | .012 | 2.078
===
# Results of Curve-Fitting Analysis Examining the Time Course of Fixations to the Target
small
[c1] align(l)
[r3-$r:c2-$c] align(r)

-- grouped column headings
[r1:c2-3;r1:c4-5] span
[r1-2:c1;r1-2:c6,7,8] span

-- rules only where they mean something
[r1;r3] line(t)
[r$r] line(b)
[r1:c2-5] line(b)

Installation

npm install ts-tableau

Preprocessors

Tableau itself only turns table markup into HTML (see Cell Content below) -- something upstream still needs to feed it that markup and hand its output to a Markdown/HTML pipeline. Known preprocessors:

  • remark-tableau -- a remark plugin that renders Tableau fenced code blocks for unified/remark-based toolchains (this is what generates the guide linked above).
  • slidev-addon-tableau -- a Slidev addon that renders Tableau fenced code blocks in presentations.
  • pandoc-tableau -- the original Pandoc/Quarto Lua filter version of Tableau, with its own markup syntax.

If you build a preprocessor for another toolchain, let me know and I'll add it here.

Using

Most consumers won't call ts-tableau directly -- see Preprocessors above for pipeline integrations (remark-tableau for unified/remark-based Markdown pipelines, or pandoc-tableau for Pandoc/Quarto). To call it yourself:

import { tableau, generate } from "ts-tableau"

const table = tableau([
  "a | b | c",
  "d | e | f",
  "===",
  "hlines vlines",
])

console.log(generate(table).join("\n"))

tableau() parses table markup (data section, then an optional === layout section) into a TableData; generate() renders that into an array of HTML lines. Load ts-tableau/tableau.css alongside the generated HTML for the table's default styling. See the guide for the full markup and layout-format reference.

Longer Cell Content

A data row can be followed by one or more col N {{ ... }} (or column N {{ ... }}) blocks, each of which replaces the content of one column of that row with a multi-line block of text, up to the matching }}. N can be a column number (col2) or a letter (colb, equivalent to column 2). The block's content is dedented by its own minimum common leading whitespace, so you can indent it to match the surrounding markup without that indentation ending up in the cell. A block that targets a column outside the row's range is silently ignored.

Cicero|||
col2 {{
  paragraph one

  paragraph two
}}

For Integrators

Cell Content

Tableau does not render Markdown inside cells itself -- it treats cell content as opaque text and wraps it in a <tableau-md>...</tableau-md> marker in its HTML output, with the content HTML-entity-escaped. This keeps the library engine-agnostic: any postprocessor (a remark plugin, a Quarto/Pandoc filter, or anything else consuming this HTML) is expected to find <tableau-md> elements, HTML-unescape their contents, run its own Markdown renderer over the result, and replace the element with the rendered output.

<tableau-md> is a valid custom-element name and is otherwise inert -- plain HTML viewers will just show its (unescaped, unrendered) text content. An empty cell produces an empty marker, <tableau-md></tableau-md>.

Styling in Host Environments

Host pages and site themes (Docusaurus, MkDocs, etc.) often apply their own default table styling -- zebra striping, borders, shadows. The shipped stylesheet (assets/tableau.css) resets background, border, and box-shadow back to neutral values on table.tableau and its tr/th/td descendants, using ordinary CSS specificity, not !important. This beats most host default table styling on its own.

It won't beat a host theme that uses !important or unusually high specificity for its own table styles. For those environments, a host-specific integration package is expected to add whatever override its environment needs (higher specificity, !important, or CSS @layer ordering, depending on what fits that host).

License

Copyright © 2023–2026, Dave Thomas (pragdave)

See LICENSE.md