Just Another Astro Markdown — remark plugins, client-side enhancements and styles as a single Astro integration.
- Installation
- Requirements
- Setup
- Integration Options
- Markdown Syntax
- MarkdownContent Component
- Client-side Enhancements
- Theming
- Manual / Advanced Usage
npm install jaamd
# or
npx astro add jaamd| Requirement | Detail |
|---|---|
| Astro | >=7.0.0 <8.0.0 |
| Toolchain | Must compile TypeScript and .astro sources |
JAAMD is published as source, with no compiled dist/ and no emitted .d.ts.
It works out of the box in any Astro project, but is not consumable from plain
Node, a CommonJS build, or a TypeScript project using
"moduleResolution": "node16".
Add the integration to your Astro config:
// astro.config.mjs
import { defineConfig } from "astro/config";
import jaamd from "jaamd";
export default defineConfig({
integrations: [jaamd()],
});Wrap your markdown content with the MarkdownContent component in your layout:
---
// src/layouts/BlogPost.astro
import MarkdownContent from "jaamd/components";
---
<MarkdownContent>
<slot />
</MarkdownContent>The integration registers all remark plugins and injects the stylesheet automatically. No other configuration is required.
Important
<MarkdownContent> loads the stylesheets for you. If you render markdown
without it, import them yourself in your layout frontmatter:
---
import "jaamd/default.css";
import "jaamd/styles.css";
---jaamd({
selector: ".jaamd-content", // CSS selector for the JS enhancements
theme: "github-light", // Shiki theme name (or { light, dark })
noDefault: false, // skip injecting jaamd/default variable fallbacks
plugins: {
codeTabs: true, // :::code-tabs directive blocks
alerts: true, // > [!NOTE] / [!WARNING] blockquote alerts
directive: true, // remark-directive (prerequisite for codeTabs)
},
})Controls which element the client-side JS enhancements target at runtime.
It does not affect the CSS file, which always uses .jaamd-content.
- With
<MarkdownContent>, leave it at the default. - With a custom wrapper (e.g.
<div data-md>), setselectorto match it and provide your own CSS.
Set to true when you supply a complete --jaamd-* variable set of your own.
It has no effect on <MarkdownContent>, which imports the defaults directly;
with a custom wrapper, control them by importing jaamd/default or not.
GitHub-style blockquote alerts, in five variants:
> [!NOTE]
> Useful information the reader should know.
> [!TIP]
> Helpful advice.
> [!IMPORTANT]
> Key information required to succeed.
> [!WARNING]
> Urgent information needing immediate attention.
> [!CAUTION]
> Advises about risks or negative outcomes.Group several code blocks into a tabbed panel. The text after the language is
used as the tab label; it falls back to the language, then to Tab N.
:::code-tabs
```bash npm
npm install
```
```bash pnpm
pnpm install
```
:::Any element with the spoiler class is hidden until activated:
<span class="spoiler">The butler did it.</span>MarkdownContent is a polymorphic component. It renders as <div> by default
and accepts any valid HTML tag via the as prop.
import MarkdownContent from "jaamd/components";| Prop | Type | Default | Description |
|---|---|---|---|
as |
HTMLTag |
"div" |
The HTML element to render as. |
class |
string |
– | Extra CSS classes appended to the wrapper. |
| ...rest | – | – | All standard HTML attributes for the chosen as element (e.g. id, data-*, aria-*). |
The jaamd-content class is always present on the wrapper element. It is the
selector used by the JS enhancements and must not be removed.
---
import MarkdownContent from "jaamd/components";
---
<!-- Default: renders as <div class="jaamd-content"> -->
<MarkdownContent>
<slot />
</MarkdownContent>
<!-- Custom tag: renders as <article class="jaamd-content"> -->
<MarkdownContent as="article">
<slot />
</MarkdownContent>
<!-- Extra classes: renders as <article class="jaamd-content prose mx-auto"> -->
<MarkdownContent as="article" class="prose mx-auto">
<slot />
</MarkdownContent>A dependency-free ES module (~2 kB gzipped) enhances the rendered markdown. It
re-runs on every astro:page-load, so it keeps working across View Transitions.
| Enhancement | Behaviour |
|---|---|
| Heading links | Adds an anchor to h1–h3; clicking copies the section URL. Fills in a missing id with a Unicode-aware slug, de-duplicated across the page. |
| Copy buttons | Adds a copy button to every pre. |
| Image lightbox | Click an image to open it full-screen. Closes on backdrop click, the ✕ button or Esc. |
| Responsive tables | Wraps every table in a horizontally scrollable container. |
| Code tabs | Drives the :::code-tabs tablist. |
| Spoilers | Reveals .spoiler content on click or Enter/Space. |
| Details | Animates the height of <details> on open/close. |
| Element | Keys |
|---|---|
| Code tabs | ← → move between tabs, Home End jump to first/last. Panels are focusable so wide samples can be scrolled. |
| Spoilers | Tab to focus, Enter or Space to reveal. Hover does not reveal. |
| Lightbox | Esc to close. |
Under prefers-reduced-motion: reduce the <details> animation is skipped and
JAAMD's CSS transitions are disabled.
Linked images are skipped automatically, so badges keep navigating:
[](https://github.com/you/repo)Opt a standalone image out with data-no-lightbox:
<img src="/diagram.svg" alt="Architecture diagram" data-no-lightbox />All styles are driven by CSS custom properties prefixed with --jaamd-*. The
default set (jaamd/default) is injected automatically.
Override any variable on :root in your own stylesheet:
:root {
--jaamd-color-fg: #334155;
--jaamd-color-fg-bright: #0f172a;
--jaamd-color-primary: #6366f1;
--jaamd-color-primary-light: #818cf8;
--jaamd-font-mono: ui-monospace, monospace;
--jaamd-font-size: 1rem;
}Defaults live in @layer jaamd.defaults, so an unlayered :root block always
wins regardless of import order.
See src/styles/variables.css for all 50 variables
and their default values.
The default set includes dark-mode overrides activated by the dark class on
<html>. Toggle the class and all JAAMD elements adapt.
Three presets restyle all --jaamd-* variables to match popular editor colour
schemes:
| Preset | Import | Recommended Shiki theme |
|---|---|---|
| Dracula | jaamd/themes/dracula |
dracula |
| Nord | jaamd/themes/nord |
nord |
| One Dark | jaamd/themes/one-dark |
one-dark-pro |
As a standalone theme, replacing the default light theme:
jaamd({ theme: "dracula", noDefault: true })@import "jaamd/themes/dracula.css";
@import "jaamd/styles.css";Scoped to html.dark via the /dark variant:
@import "jaamd/themes/dracula/dark.css";import "jaamd/themes/dracula/dark";Copy any preset from src/themes/ to customise it.
Pass an object to theme to highlight code with two colour schemes, switched by
the dark class:
jaamd({
theme: { light: "github-light", dark: "github-dark" },
})Add an inline script in <head> to prevent a flash of the wrong theme:
<script is:inline>
(function () {
var t = localStorage.getItem("theme");
if (t === "dark" || (!t && matchMedia("(prefers-color-scheme: dark)").matches))
document.documentElement.classList.add("dark");
})();
</script>To bypass the integration, register the plugins yourself. Astro's default
markdown processor does not run markdown.remarkPlugins, so pass the unified
processor from @astrojs/markdown-remark explicitly:
// astro.config.mjs
import { unified } from "@astrojs/markdown-remark";
import { remarkCodeTabs, remarkAlert, remarkDirective } from "jaamd";
export default defineConfig({
markdown: {
processor: unified({
remarkPlugins: [remarkAlert, remarkDirective, remarkCodeTabs],
}),
},
});---
import "jaamd/default"; // variable fallbacks; omit if you provide your own
import "jaamd/styles";
---
<div class="jaamd-content">
<slot />
</div>
<script>
import { initMarkdownEnhancements } from "jaamd/client";
function run() { initMarkdownEnhancements(".jaamd-content"); }
run();
document.addEventListener("astro:page-load", run);
</script>The CSS files can also be imported from plain .css:
@import "jaamd/default.css";
@import "jaamd/styles.css";