---
title: "remark-rehype-github-markdown-theme-aware-assets"
description: "Render GitHub theme-aware assets using proper HAST nodes and remark/rehype (unified) plugins."
canonical_url: "https://neg4n.dev/projects/remark-rehype-github-markdown-theme-aware-assets"
md_url: "https://neg4n.dev/projects/remark-rehype-github-markdown-theme-aware-assets.md"
---

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

## Resources

- [GitHub](https://github.com/neg4n/remark-rehype-github-markdown-theme-aware-assets)
- [npm](https://www.npmjs.com/package/remark-rehype-github-markdown-theme-aware-assets)

## README

remark-rehype-github-markdown-theme-aware-assetsRender precomputed asset links into   /   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 API – renderAsset / 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 plugins – remarkAssetEmbed 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.Installationpnpm add remark-rehype-github-markdown-theme-aware-assets
# or
npm install remark-rehype-github-markdown-theme-aware-assetsData Modeltype ThemedAsset = {
  alt: string
  href: string
  metadata?: Record 
  includeThemedPicture?: true // default: themed  
  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 
}

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 safetyThe 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 Usageimport {
  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   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 manipulationAPI at a glancenormalizeAssets(assets) → validated Asset[]buildAssetNodes(assets, options?) → ElementContent[]renderAsset(asset, options?) → HTML stringrenderAssets(assets, options?) → HTML stringrenderAssetDetailed(asset, options?) → { html, node, asset, options }renderAssetsDetailed(assets, options?) → { html, nodes, assets, options }getFallbackSrc(asset, baseTheme) – helper to pick the right fallback imageisAssetValidationError(error) → booleanUnified IntegrationremarkTransform 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.rehypeInject   /   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 HandlingnormalizeAssets (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.LicenseMIT
