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

@reblu/site-blocks

v0.4.4

Published

Data-driven presentational organisms for Reblu SITEs (Collection, MarkdownRenderer, Treatment, Specialty, ProfileCard, ProductCard) — clean, token-driven, white-label.

Readme

@reblu/site-blocks

Data-driven presentational organisms for Reblu SITEs — the layer above @reblu/site-ui (atoms/primitives).

These blocks are clean and token-driven: they carry no brand aesthetic of their own. Every color, radius and font comes from CSS design tokens (OKLCH) that each tenant injects through its site-theme.css. The same package renders a different-looking site for every tenant, with zero forks.

What's inside

| Export | Entry | Purpose | | ------ | ----- | ------- | | readingTime, formatPrice, buildShareUrls, markdownToSafeHtml | . | Pure, framework-agnostic helpers (RSC-safe). | | sanitizeHtml | . | Sanitises an HTML string before dangerouslySetInnerHTML — the HTML boundary. See "Security boundaries" below. | | MarkdownRenderer | . | Canonical Blog renderer: marked → hast-util-sanitize (XSS) → component map, with a YouTube-domain allowlist. RSC-safe. | | Treatment, Specialty, ProfileCard, ProductCard | . | Presentational organisms over the SITE API contracts. RSC-safe. | | useCollection | ./client | Headless filterable/sortable/paginated collection hook (facets, category, tag, search). Serves both page and blog indexes. | | Collection | ./client | Token-driven presentational grid over useCollection. | | ShareButtons | ./client | Social share (X / LinkedIn / WhatsApp / copy). |

Boundary

  • @reblu/site-ui = atoms (Button, Input, Link, Form, cn). Stable.
  • @reblu/site-blocks = organisms + headless data hooks. Higher churn — its own package so a breaking change in an organism does not bump the stable atoms.

Authoring notes

What MarkdownRenderer does with tenant-authored Markdown and HTML. These are content rules, not styling ones — they change what a page says, so they matter most on legal pages (privacy policy, terms), which nobody on the platform side proofreads before a visitor sees them.

A single newline is not a line break

Calle Mayor 1
28001 Madrid

renders as one paragraph reading Calle Mayor 1 28001 Madrid. This is standard CommonMark — a lone newline is a soft break, i.e. whitespace — and it is what this renderer has done since it replaced the interim, non-standard one that emitted a <br> for every newline. Text written under the old behaviour reads differently now, so addresses, signature blocks and enumerated clauses typed on consecutive lines are worth re-reading.

To force a visible break, use any of:

| Want | Write | | ---- | ----- | | A new paragraph | A blank line between the two lines | | A hard break inside one paragraph | Two spaces at the end of the first line | | A hard break, visibly | A trailing \ at the end of the first line |

hidden hides a block — it does not withhold it

An element marked hidden is not rendered and is not announced to a screen reader. It is still present in the HTML the server sends. Anyone who opens the page source reads it, and so does any crawler working on raw HTML.

That is exactly what the HTML specification says hidden means, and for most uses it is the right behaviour. It is called out here because the wording in 0.4.0's changelog — "a block the author marked as not-for-publication stays unpublished" — promises more than the attribute delivers, and an author who believes it will reach for hidden to do something it cannot do.

So: a half-written clause left in a privacy policy behind hidden is invisible on the page and published all the same. To keep text unpublished, keep it out of the page. Draft it somewhere that is not the document body.

Ordered lists keep the number you wrote

4. … renders as a list starting at 4, not at 1 — so "as established in clause 4" still points at a 4. The marker style (type) and an item's explicit number (value) survive too.

Which authored attributes survive

The renderer rebuilds each element to apply the tenant's design tokens and to harden external links, and it forwards only the attributes that carry meaning:

  • Kept: id (so in-document #anchor links resolve), lang, dir, title, hidden, role, every aria-* and every data-*, plus start/type on <ol> and value/type on <li>.
  • Dropped: class and style — presentation belongs to the tenant's theme, not to the document body — and target/rel on links, which the renderer sets itself so an external link is always noopener noreferrer.
  • Dropped upstream by the sanitiser, before the renderer sees it: reversed on <ol>. Number a descending list explicitly with value on each <li>.

Security boundaries

Two sinks, two different helpers, in two different packages — do not reach for one where the other belongs:

| Sink | Helper | Package | | ---- | ------ | ------- | | HTML rendered via dangerouslySetInnerHTML | sanitizeHtml (strip/allowlist sanitiser) | @reblu/site-blocks (here) | | <script type="application/ld+json"> body | jsonLdScriptContent / <JsonLd> (escaper) | @reblu/site-next/json-ld |

A <script> body is HTML's raw-text context: the parser ends it on the first </script byte sequence regardless of whether that sequence sits inside a JSON string, so JSON.stringify-ing tenant data straight into a JSON-LD <script> is a stored-XSS sink sanitizeHtml does not cover (it sanitises markup, not JSON). jsonLdScriptContent lives in @reblu/site-next rather than here: it has zero dependencies of its own, and this package's main entry bundles parse5 + the hast-util-* chain + marked for sanitizeHtml (treeshake: false, ~463 KB unminified) — importing the escaper from here would drag that parser chain behind a 40-line function.

Types come from @reblu/site-contracts (the SDK view of the /api/site/* responses). No Zod and no Prisma reach the consumer runtime.