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

remark-rehype-github-markdown-theme-aware-assets

v1.0.0

Published

Render GitHub-ready badge/image/asset markup with processor-agnostic utilities with optional remark/rehype plugins (unified ecosystem)

Readme

remark-rehype-github-markdown-theme-aware-assets

Render precomputed asset links into <picture> / <img> HTML backed by HAST nodes that honour light/dark themes - especially useful when embedding theme-aware media resources inside GitHub's README. The core builder produces hastscript elements first, then offers an optional stringifier for consumers outside the Unified pipeline.

Features

  • 🎯 Asset-first APIrenderAsset / renderAssets produce stable markup without any collection/section abstractions.
  • 🧩 HTML + HAST helpers – Build reusable hastscript nodes and optionally stringify them for environments outside Unified.
  • 🔌 Unified-ready pluginsremarkAssetEmbed and rehypeAssetEmbed transform nodes exposing data.assets into rendered content (replace, append, or wrap in-place).
  • 🛡️ Structured runtime errors – Validation problems surface as AssetValidationError instances; plugins downgrade them to vfile messages so pipelines keep running.
  • 🧪 Snapshot-tested – Vitest coverage verifies renderer output and integration scenarios.

Installation

pnpm add remark-rehype-github-markdown-theme-aware-assets
# or
npm install remark-rehype-github-markdown-theme-aware-assets

Data Model

type ThemedAsset = {
  alt: string
  href: string
  metadata?: Record<string, unknown>
  includeThemedPicture?: true // default: themed <picture>
  srcLight?: string
  srcDark?: string
  baseTheme?: 'light' | 'dark' // required when only one themed asset exists
}

type MarkdownAsset = {
  alt: string
  href: string
  includeThemedPicture: false
  src: string
  metadata?: Record<string, unknown>
}

type Asset = ThemedAsset | MarkdownAsset

type AssetRenderOptions = {
  includeThemedPicture?: boolean // default true
  baseTheme?: 'light' | 'dark' // default 'dark'
  singleLineOutput?: boolean // default false
  indent?: string // for multi-line HTML output
}

When includeThemedPicture is omitted (the default), provide themed sources via srcLight and/or srcDark. If only one variant exists, set baseTheme so fallback images are picked correctly. Trying to mix themed/markdown fields is caught at runtime by the built-in validator.

Input safety
The library does not escape or sanitize strings. Provide already-safe values for alt, href, and src* fields that are suitable for direct insertion into HTML.

Core Usage

import {
  buildAssetNodes,
  renderAsset,
  renderAssets,
  renderAssetDetailed,
  renderAssetsDetailed,
} from 'remark-rehype-github-markdown-theme-aware-assets'

const ci = {
  alt: 'CI Status',
  href: 'https://github.com/acme/project/actions',
  srcLight: 'https://img.shields.io/github/actions/workflow/status/acme/project/ci.yml?theme=light',
  srcDark: 'https://img.shields.io/github/actions/workflow/status/acme/project/ci.yml?theme=dark',
}

const docs = {
  alt: 'Documentation',
  href: 'https://acme.dev/docs',
  src: 'https://img.shields.io/badge/docs-success.svg',
  includeThemedPicture: false,
}

renderAsset(ci)
// => themed <picture> markup string (multi-line by default)

renderAssets([ci, docs], { singleLineOutput: true })
// => both assets rendered on a single line, separated by a space

renderAssetDetailed(ci, { baseTheme: 'light' })
// => { html, node, asset, options } without recomputing

const result = renderAssetsDetailed([ci, docs])
// result.html -> combined output
// result.nodes -> HAST nodes (including separators)
// result.assets[1].options.includeThemedPicture === false

const nodes = buildAssetNodes([ci, docs])
// => ElementContent[] suitable for manual HAST manipulation

API at a glance

  • normalizeAssets(assets) → validated Asset[]
  • buildAssetNodes(assets, options?)ElementContent[]
  • renderAsset(asset, options?) → HTML string
  • renderAssets(assets, options?) → HTML string
  • renderAssetDetailed(asset, options?){ html, node, asset, options }
  • renderAssetsDetailed(assets, options?){ html, nodes, assets, options }
  • getFallbackSrc(asset, baseTheme) – helper to pick the right fallback image
  • isAssetValidationError(error) → boolean

Unified Integration

remark

Transform placeholder nodes that carry an assets array in their data into rendered HTML strings:

import { remark } from 'remark'
import remarkParse from 'remark-parse'
import remarkStringify from 'remark-stringify'
import { remarkAssetEmbed } from 'remark-rehype-github-markdown-theme-aware-assets'

const processor = remark()
  .use(remarkParse)
  .use(() => tree => {
    tree.children.push({
      type: 'asset-placeholder',
      data: { assets: [ci, docs] },
      children: [],
    })
  })
  .use(
    remarkAssetEmbed({
      injectionMode: 'wrap', // 'replace' | 'append' | 'wrap'
      wrapTagName: 'div',
      wrapClassName: 'asset-grid',
    })
  )
  .use(remarkStringify)

The plugin swaps the placeholder for raw HTML string output. Validation issues show up as vfile.message entries so your pipeline can decide how to proceed.

rehype

Inject <picture> / <img> nodes directly into HAST while keeping render metadata on the original node:

import { rehype } from 'rehype'
import rehypeStringify from 'rehype-stringify'
import { rehypeAssetEmbed } from 'remark-rehype-github-markdown-theme-aware-assets'

const processor = rehype()
  .data('settings', { fragment: true })
  .use(() => tree => {
    tree.children.push({
      type: 'element',
      tagName: 'div',
      data: { assets: [ci, docs] },
      children: [],
    })
  })
  .use(
    rehypeAssetEmbed({
      injectionMode: 'replace',
      wrapTagName: 'div',
      wrapProperties: { className: ['asset-wrapper'] },
    })
  )
  .use(rehypeStringify)

Each processed node receives an assetRenderResult with the combined markup and per-asset metadata, which can be inspected by downstream plugins.

Error Handling

normalizeAssets (and every renderer built on top of it) throws an AssetValidationError when:

  • the asset list is empty or undefined,
  • an entry is not an object,
  • required fields (alt, href, src for markdown assets) are missing,
  • theme-specific constraints are violated (e.g. srcLight without includeThemedPicture).

Each error carries a machine-readable code plus a path pointing to the offending field. Unified plugins catch these errors and emit human-friendly messages instead of crashing.

License

MIT