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

@k11k/better-blocks-astro-renderer

v0.7.0

Published

Astro renderer for Strapi v5 Blocks content with full Better Blocks plugin support — colors, tables, to-do lists, media embeds, alignment, and more. Native Astro components, zero client-side JavaScript.

Readme


Table of Contents

  1. Why?
  2. Compatibility
  3. Installation
  4. Usage
  5. Supported Blocks
  6. Supported Modifiers
  7. Custom Renderers
  8. TypeScript
  9. Contributing
  10. Support this project
  11. License

Why?

The official Strapi blocks renderers are built for React. If your site is built with Astro, you can render Strapi blocks through the @astrojs/react integration — but that pulls React into your build for what is purely presentational content.

This package is a native Astro renderer. It renders Strapi v5 Blocks content — including every feature the Better Blocks plugin adds (color marks, text alignment, to-do lists, tables, media embeds, and more) — using plain .astro components. The output is static HTML with zero client-side JavaScript, and math is rendered to a string on the server (see Math (KaTeX)).

It is a drop-in renderer that handles all Better Blocks features out of the box — no configuration needed.

Compatibility

| Strapi Version | Renderer Version | Astro Version | | -------------- | ---------------- | ------------- | | v5.x | v0.x | ≥ 4 |

Installation

# Using yarn
yarn add @k11k/better-blocks-astro-renderer

# Using npm
npm install @k11k/better-blocks-astro-renderer

Peer dependencies: astro >= 4

Usage

---
import { BlocksRenderer } from '@k11k/better-blocks-astro-renderer';

const { blocks } = Astro.props;
---

<BlocksRenderer content={blocks} />

That's it. All Better Blocks features — colors, tables, to-do lists, media embeds, alignment, and more — work automatically, and the component renders to static HTML (no hydration, no client directive).

A typical page that fetches from Strapi:

---
import { BlocksRenderer, type BlocksContent } from '@k11k/better-blocks-astro-renderer';
// Import the KaTeX stylesheet once (e.g. in a shared layout) so math displays correctly.
import 'katex/dist/katex.min.css';

const res = await fetch('https://your-strapi.example.com/api/articles?status=published');
const { data } = await res.json();
---

{
  data.map((article: { content: BlocksContent }) => (
    <article>
      <BlocksRenderer content={article.content} />
    </article>
  ))
}

Math (KaTeX)

Math nodes are rendered with KaTeX — inline math becomes a <span class="katex-inline"> and block math a <div class="katex-block">. Rendering happens via katex.renderToString on the server, so it works during SSR and static builds with no client-side hydration step.

KaTeX needs its stylesheet to display correctly. Import it once in your app (for example in a shared layout):

---
import 'katex/dist/katex.min.css';
---

katex ships as a dependency of this package, so the stylesheet resolves without a separate install. If KaTeX fails to parse a formula, the renderer falls back to the raw LaTeX source instead of crashing.

Diagrams (Mermaid)

Mermaid diagram blocks ({ type: 'diagram', format: 'mermaid' }) are pre-rendered to inline SVG on the server using beautiful-mermaid — a pure-Node renderer that needs no headless browser (no Puppeteer, no Chromium download). Like math, rendering happens during SSR and static builds with zero client-side JavaScript and no hydration step.

Supported diagram types — flowchart, sequence, state, class, ER, and xychart — render to a <div class="mermaid-diagram"> wrapping the generated SVG. Diagram types beautiful-mermaid does not implement yet (gantt, pie, mindmap, gitGraph, …) and any source that fails to parse fall back gracefully to the raw definition in a <pre class="mermaid-source">, so content is never lost.

beautiful-mermaid ships as a dependency of this package, so no extra install or stylesheet is required.

Diagram colors

Diagrams render in color by default, with a palette that mirrors mermaid.js's familiar look (lavender node fills, purple borders, dark edges). Pass diagramTheme to pick a built-in palette (github-light, github-dark, dracula, nord, tokyo-night, catppuccin-mocha, solarized-light, …) or a custom color object ({ bg, fg, line, accent, muted, surface, border }):

---
import { BlocksRenderer } from '@k11k/better-blocks-astro-renderer';
const { blocks } = Astro.props;
---

<!-- built-in theme -->
<BlocksRenderer content={blocks} diagramTheme="github-dark" />

<!-- or a custom palette -->
<BlocksRenderer content={blocks} diagramTheme={{ bg: '#fff', fg: '#1f2328', accent: '#8250df' }} />

beautiful-mermaid derives a clean, single-accent palette from these colors — it is intentionally minimal, not a 1:1 clone of mermaid.js's multi-color default theme. To take full control of the markup (e.g. to render with the real mermaid.js on the client), override the diagram block via blocks.diagram.

Callouts (Admonitions)

Block-level callout nodes render GitHub-style alerts in five variants — note, tip, important, warning, and caution. Each renders as an <aside role="note"> with a colored left border, a title row (icon + label), and the nested block children (paragraphs, lists, links, etc.). If a title is set on the node it is used; otherwise the localized variant label is shown.

Colors come from a small scoped <style> that ships with the component (still zero client-side JavaScript), and the default palette adapts to dark mode automatically via @media (prefers-color-scheme: dark). The accent for each variant is driven by a --bb-callout-accent custom property on the .bb-callout-{variant} element, so you can retheme colors from your own CSS without replacing the markup:

/* Recolor a single variant, or override per color scheme */
.bb-callout-note {
  --bb-callout-accent: #2563eb;
}

To replace the markup entirely, override the callout block. It receives variant and title; the nested children arrive via <slot />:

---
import { BlocksRenderer } from '@k11k/better-blocks-astro-renderer';
import MyCallout from '../components/MyCallout.astro';
const { blocks } = Astro.props;
---

<BlocksRenderer content={blocks} blocks={{ callout: MyCallout }} />

Details / Summary (Collapsible)

Block-level details nodes render a native, keyboard-accessible <details> / <summary> disclosure with zero client-side JavaScript — the open/closed state is handled entirely by the browser. The summary field is the plain-text label, the optional defaultOpen boolean maps to the HTML open attribute (honored on initial render so screen readers get the correct state), and children are block-level content (paragraphs, lists, tables, images, and nested details) rendered after the summary. The default markup carries stable bb-details and bb-details-summary classes.

A small scoped <style> ships with the component (still zero client-side JavaScript): a GitHub-inspired card with a rotating disclosure marker. Retheme it from your own CSS via the --bb-details-* custom properties (--bb-details-border, --bb-details-bg, --bb-details-summary-bg, --bb-details-marker) without replacing the markup:

.bb-details {
  --bb-details-border: #c8c8c8;
  --bb-details-summary-bg: #eee;
}

To replace the markup entirely, override the details block. It receives summary and defaultOpen; the nested children arrive via <slot />:

---
import { BlocksRenderer } from '@k11k/better-blocks-astro-renderer';
import MyDetails from '../components/MyDetails.astro';
const { blocks } = Astro.props;
---

<BlocksRenderer content={blocks} blocks={{ details: MyDetails }} />

Buttons (CTA & File Download)

Block-level button nodes render a WordPress-style call-to-action as a single, accessible <a> (or a styled <span> when no target is set). Two modes are driven by buttonType:

  • Link (buttonType: 'link') → <a href={link.url} target rel aria-label> for a normal CTA. rel="noopener noreferrer" is honored when present (the editor adds it automatically for target="_blank").
  • File (buttonType: 'file') → a download link (<a href={file.url} download={file.name}>) for a Media Library asset, optionally prefixed with a file-type icon (showFileIcon) and suffixed with a human-readable size (showFileSize, e.g. (5 MB)).

Download vs. preview

By default a file button force-downloads the asset. The native download attribute only works same-origin, so for cross-origin assets (Strapi/CDN) browsers ignore it and open renderable files (PDF, video, images) inline. To fix that, download-mode buttons are tagged data-bb-download and a tiny scoped <script> (the renderer's only client-side JavaScript) fetches the asset as a blob and saves it from a same-origin object URL. This is progressive enhancement: without JS the anchor still works via its href + download attributes, and a CORS-blocked fetch falls back to native navigation.

Set filePreview: true to instead open the file in a new tab (target="_blank" rel="noopener noreferrer", no download) so users can preview it before saving — this path is fully zero-JS.

The optional style object is applied as inline CSS (backgroundColor, textColor, borderRadius, fontSize, fontWeight, padding, border), and alignment (left / center / right) wraps the button in a text-aligned .bb-button-wrapper (none renders it inline with no wrapper). A cssClass is appended to the default bb-button class for theming.

Because inline styles can't express :hover, the hoverBackgroundColor / hoverTextColor are exposed as --bb-button-hover-bg / --bb-button-hover-color custom properties, and a scoped <style> wires up the hover transition and a visible keyboard focus ring by default — no extra CSS required.

To replace the markup entirely, override the button block. It receives label, buttonType, alignment, link, file, showFileSize, showFileIcon, filePreview, style, and cssClass as props:

---
import { BlocksRenderer } from '@k11k/better-blocks-astro-renderer';
import MyButton from '../components/MyButton.astro';
const { blocks } = Astro.props;
---

<BlocksRenderer content={blocks} blocks={{ button: MyButton }} />

Supported Blocks

| Block | Default element | Source | | ------------------------------- | -------------------- | --------------------------- | | paragraph | <p> | Strapi core | | heading (1–6) | <h1><h6> | Strapi core | | list (ordered/unordered/todo) | <ol> / <ul> | Strapi core + Better Blocks | | list-item | <li> | Strapi core | | link | <a> | Strapi core | | quote | <blockquote> | Strapi core | | code | <pre><code> | Strapi core | | image | <figure><img> | Strapi core | | horizontal-line | <hr> | Better Blocks | | table | <table> | Better Blocks | | media-embed | <iframe> (16:9) | Better Blocks | | math (inline/block) | <span> / <div> | Better Blocks | | diagram (mermaid) | <div> (inline SVG) | Better Blocks | | callout (admonition) | <aside> | Better Blocks | | details (collapsible) | <details> | Better Blocks | | button (CTA / file download) | <a> / <span> | Better Blocks |

Block properties

| Property | Applies to | Description | | -------------- | ------------------------- | --------------------------------------------------------- | | textAlign | paragraph, heading, quote | Text alignment (left, center, right, justify) | | lineHeight | paragraph, heading, quote | CSS line-height value (e.g. 1.5, 2.0) | | indent | paragraph, heading, quote | Block indentation level (marginLeft: N * 2rem) | | indentLevel | list | Cycling list-style-type per nesting depth | | format | list | ordered, unordered, or todo | | checked | list-item (in todo lists) | Checkbox state (true/false) | | target | link | _blank for new-tab links | | rel | link | noopener noreferrer for new-tab links | | caption | image | Text displayed below the image | | imageAlign | image | Image alignment (left, center, right) | | url | media-embed | Embed URL (YouTube/Vimeo iframe src) | | originalUrl | media-embed | Original user-provided URL | | format | math | inline (<span>) or block (<div>) | | value | math | LaTeX source rendered with KaTeX | | format | diagram | mermaid (the only supported diagram format) | | value | diagram | Mermaid source, pre-rendered to SVG on the server | | summary | details | Plain-text label for the <summary> | | defaultOpen | details | Open on initial render (HTML open attribute) | | buttonType | button | link (CTA) or file (Media Library download) | | label | button | Visible button text | | alignment | button | left, center, right, or none (inline) | | link | button | { url, target?, rel?, ariaLabel? } (link mode) | | file | button | { url, name, size?, ext?, mime? } (file mode) | | showFileIcon | button | Prefix a file-type icon (file mode) | | showFileSize | button | Suffix a human-readable size, e.g. (5 MB) | | filePreview | button | true opens the file in a new tab instead of downloading | | style | button | Inline CSS + hover* colors via custom properties | | cssClass | button | Extra class appended to bb-button |

Supported Modifiers

| Modifier | Default element | Source | | ----------------- | --------------------------------- | ------------- | | bold | <strong> | Strapi core | | italic | <em> | Strapi core | | underline | <span> | Strapi core | | strikethrough | <del> | Strapi core | | code | <code> | Strapi core | | uppercase | <span style="text-transform"> | Better Blocks | | superscript | <sup> | Better Blocks | | subscript | <sub> | Better Blocks | | color | <span style="color"> | Better Blocks | | backgroundColor | <span style="background-color"> | Better Blocks | | fontFamily | <span style="font-family"> | Better Blocks | | fontSize | <span style="font-size"> | Better Blocks |

Custom Renderers

Override any block type or text modifier with your own Astro component. Pass a map of type → component via the blocks and modifiers props. Each custom component receives its props through Astro.props and its inner content through the default <slot />.

Custom block renderers

---
import { BlocksRenderer } from '@k11k/better-blocks-astro-renderer';
import MyParagraph from '../components/MyParagraph.astro';
import MyImage from '../components/MyImage.astro';
import MyTable from '../components/MyTable.astro';

const { blocks } = Astro.props;
---

<BlocksRenderer
  content={blocks}
  blocks={{
    paragraph: MyParagraph,
    image: MyImage,
    table: MyTable,
  }}
/>
---
// src/components/MyImage.astro
const { image, caption, imageAlign } = Astro.props;
---

<figure style={{ textAlign: imageAlign }}>
  <img src={image.url} alt={image.alternativeText || ''} loading="lazy" />
  {caption && <figcaption>{caption}</figcaption>}
</figure>

The props each custom block component receives:

| Block | Props (plus <slot /> for children where applicable) | | ---------------------------------------------------------- | ------------------------------------------------------------- | | paragraph | { style?} | | heading | { level: 1–6; style? } | | list | { format: 'ordered' \| 'unordered' \| 'todo'; indentLevel } | | list-item | { checked? } | | link | { url; target?; rel? } | | quote | { style? } | | code | { plainText } (also via <slot />) | | image | { image; caption?; imageAlign? } (no slot) | | horizontal-line | none | | table / table-row / table-cell / table-header-cell | children via <slot /> | | media-embed | { url; originalUrl? } (no slot) | | math | { formula; inline } (no slot) — bring your own math engine | | diagram | { code; format } (no slot) — bring your own diagram engine |

Custom modifier renderers

---
import { BlocksRenderer } from '@k11k/better-blocks-astro-renderer';
import Highlight from '../components/Highlight.astro';

const { blocks } = Astro.props;
---

<BlocksRenderer content={blocks} modifiers={{ backgroundColor: Highlight }} />
---
// src/components/Highlight.astro
const { backgroundColor } = Astro.props;
---

<mark style={{ backgroundColor }}><slot /></mark>

The color/size/font modifiers receive a value prop (color, backgroundColor, fontFamily, fontSize); the rest receive only their <slot />.

TypeScript

All types are exported:

import type {
  BlocksContent,
  BlocksRendererProps,
  BlockNode,
  TextNode,
  LinkNode,
  ListNode,
  ListItemNode,
  ParagraphNode,
  HeadingNode,
  QuoteNode,
  CodeNode,
  ImageNode,
  HorizontalLineNode,
  TableNode,
  TableRowNode,
  TableCellNode,
  TableHeaderCellNode,
  MediaEmbedNode,
  MathNode,
  DiagramNode,
  TextAlign,
  CustomBlocksConfig,
  CustomModifiersConfig,
} from '@k11k/better-blocks-astro-renderer';

Contributing

Contributions are welcome! The easiest way to get started is with Docker:

# Clone the repository
git clone https://github.com/k11k-labs/better-blocks-astro-renderer.git
cd better-blocks-astro-renderer

# Start the playground with Docker
cd playground
docker compose up

This will start a Strapi v5 instance with the Better Blocks plugin and an Astro app that renders the content — all pre-configured with a showcase article.

  • Strapi admin: http://localhost:1337/admin (login: [email protected] / admin12#)
  • Astro app: http://localhost:4321

Development workflow

  1. Edit the .astro components in src/
  2. The Astro app picks up the change automatically — there is no build step

Without Docker

# Install dependencies (no build step — the renderer ships .astro source)
yarn install

# Start Strapi
cd playground/strapi && cp .env.example .env && npm install && npm run dev

# Start the Astro app (in another terminal)
cd playground/astro-app && npm install && npm run dev

Running tests

yarn test        # Run tests (Astro container API + Vitest)
yarn test:ts     # Type check (astro check)
yarn lint        # Check formatting

Community & Support

Related

Support this project

This package is built and maintained in my free time, and it's free for everyone. If it has saved you time on a project, you can help keep it caffeinated and actively developed:

Every coffee goes toward fixing bugs, reviewing PRs, writing docs, and shipping the features you ask for. Thank you! ☕

License

MIT License © k11k-labs