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

consolefmt

v1.0.1

Published

Rich, styled content in the devtools console, via Custom Formatters.

Readme

consolefmt

Rich, styled content in the devtools console.

[!WARNING] Deprecated as of Chrome 153 (September 2026). consolefmt is no longer maintained.

For eleven years, custom formatters rendered inline styles verbatim, which is what let this library lay out real CSS in the console. Chrome 153 ended that. Formatter styles now go through the same CSS allowlist as console.log("%c", …), which drops layout properties like display, so the gallery examples no longer render as shown.

This is intentional security hardening against a page restyling the DevTools UI, with no opt-out (no flag, experiment, or policy). Firefox filters formatter styles too — with a broader allowlist that still drops grid, flex, and transforms.

A devtools console showing four things consolefmt drew: a flame graph of a 420ms profile, an event loop trace with one task over the frame budget, five compositing layers tilted in 3D, and a railroad diagram of a URL regex

Every one of those is a console.log, and there are plenty more in the gallery. Run them in your own console on the playground.

Origin story

console.log can already do some of this on its own. Each %c takes its CSS from the next argument and styles the text that follows it. The top bar of that flame graph is one %c:

console.log("%cmain %sms", "background: #e8590c; color: #3d2600; padding: 0 5px", 420);

That works for a single bar, but the row underneath it is three bars, and they all go into the same format string, each with a "" after it to switch the styling back off:

console.log("%cparse %sms%c %crender %sms%c %cidle %sms",
  bar, 60, "", bar, 300, "", bar, 60);

You can build helpers that make %c easier to use, but you still can't nest one inside another. A %c changes the styling from that point on, so a message comes out as a flat run of styled spans. There is no row to put bars in, and no panel to put rows in.

The sizes would be wrong anyway: a %c only honours a handful of CSS properties, and width is not one of them, so the bars come out as wide as their labels. Padding fakes it but only up to a point.

Enter custom formatters, an API that shipped with Chrome in 2015 and lets a value say how it should appear in the console. A ClojureScript map or an Immutable.js list can print as itself instead of as its internals. A formatter returns an HTML element structure, without the %c restrictions.

consolefmt leverages that API for logging rather than for data types. You build a message out of HTML and CSS that renders the way you expect it to.

Enable custom formatters

Custom formatters work in Chromium browsers and Firefox, but behind a setting. Once per browser: open devtools, then Settings → Enable custom formatters, and reload the page. Nothing renders until you do.

Install

From npm:

npm install consolefmt
import consolefmt from "consolefmt";

Or from a CDN, without a build step:

<script type="module">
  import consolefmt from "https://cdn.jsdelivr.net/npm/consolefmt@1/dist/consolefmt.esm.js";
</script>

Or as a classic script, which defines a consolefmt global:

<script src="https://cdn.jsdelivr.net/npm/consolefmt@1/dist/consolefmt.js"></script>
<script>
  console.log(consolefmt.span({ color: "crimson" }, "hello"));
</script>

Both builds are minified, and their sourcemaps carry the TypeScript, so consolefmt's own frames read as source in devtools.

Your first message

For every HTML tag consolefmt provides a function that returns an object the console renders as that element:

const { span } = consolefmt;

console.log(span({ color: "crimson", fontWeight: "bold" }, "hello"));

The word hello printed in bold crimson in the devtools console

A call takes a plain object of CSS properties first, then its children. Anything else in first position is a child too, so span("hello") works without passing an empty object first.

Messages nest

Every argument after the attributes is a child, and a child can be another element. That is how a message becomes more than a line.

const { div } = consolefmt;

console.log(
  div({ padding: "6px 10px", borderLeft: "3px solid #c0392b",
        background: "#fdf6f6", color: "#5a2f2f" },
    div({ fontWeight: "bold" }, "payment declined"),
    div({ marginTop: "4px" }, "card expired, retrying in 30s"),
  ),
);

Build your own elements

Wrap a structure in a function and you have an element of your own, usable like any other.

const { span } = consolefmt;

const badge = (text, color) => span({
  color: "white", background: color, fontWeight: "bold",
  padding: "1px 7px", borderRadius: "10px", fontSize: "11px",
}, text);

console.log(span(badge("READY", "#27ae60"), " server listening on :3000"));
console.log(span(badge("SLOW", "#e67e22"), " GET /api/orders took 2.4s"));

extend does the same for attributes, giving you a tag with defaults already applied:

const box = td.extend({ padding: "3px 10px" });
const head = box.extend({ fontWeight: "bold" });

Log the object itself

An object inside a message stays live. It is a reference to the real thing rather than a snapshot of its text, so you can open it in the console and walk it. It arrives collapsed, as ▸ Object.

const { div, span } = consolefmt;

const badge = (text, color) => span({
  color: "white", background: color, fontWeight: "bold",
  padding: "1px 7px", borderRadius: "10px", fontSize: "11px",
}, text);

const order = { id: 8812, total: 42.5, items: ["hat", "scarf"] };

console.log(
  div({ padding: "6px 10px", borderLeft: "3px solid #c0392b",
        background: "#fdf6f6", color: "#5a2f2f" },
    div(badge("FAIL", "#c0392b"),
        span({ fontWeight: "bold" }, " payment declined")),
    div({ marginTop: "4px" }, span({ color: "#8a8a8a" }, "order "), order),
  ),
);

An object in the first argument is read as attributes. Pass empty attributes to put one there: span({}, order).

Grids

grid is a thin layer over display: grid, for building tables whose cells span. The formatters API renders table, tr and td, but ignores colspan and rowspan, so a real table cannot span its cells.

const { grid } = consolefmt;
const { row, cell } = grid;
const box = cell.extend({ padding: "3px 10px", background: "white" });
const head = box.extend({ fontWeight: "bold", background: "#f4f4f4" });
const heat = (n) => box({ textAlign: "right",
  background: `rgb(220, ${255 - n * 2}, ${255 - n * 2})` }, n + "ms");

console.log(
  grid({ gap: "1px", background: "#ddd", border: "1px solid #ddd" },
    row(head("endpoint"), head("p50"), head("p95"), head("p99")),
    row(box("/api/users"), heat(12), heat(48), heat(91)),
    row(box("/api/orders"), heat(20), heat(33), heat(70)),
    row(box({ colspan: 3, fontWeight: "bold", textAlign: "right" }, "worst"), heat(91)),
  ),
);

colspan and rowspan are named after the table attributes they stand in for, and become grid-column and grid-row. A cell covered by a rowspan above is left out of its row, the same as in HTML.

Where to go next

Thirty worked examples in the gallery, from a session log to a flame graph to a layer inspector tilted in 3D. Every one of them is a console.log, and the playground runs any of them in your own console without installing anything.

Licence

MIT