Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/components/tabs/MarkdownProcessorTabs.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
import { Tabs, TabItem } from '@astrojs/starlight/components';
---

<Tabs syncKey="markdown-processor">
<TabItem label="Sätteri">
<slot name="satteri" />
</TabItem>
<TabItem label="Unified">
<slot name="unified" />
</TabItem>
</Tabs>
15 changes: 8 additions & 7 deletions src/content/docs/en/guides/markdown-content.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import Since from '~/components/Since.astro';
import { FileTree } from '@astrojs/starlight/components';
import RecipeLinks from "~/components/RecipeLinks.astro";
import ReadMore from '~/components/ReadMore.astro';
import { Steps, Tabs, TabItem } from '@astrojs/starlight/components';
import { Steps } from '@astrojs/starlight/components';
import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro';
import MarkdownProcessorTabs from '~/components/tabs/MarkdownProcessorTabs.astro';

[Markdown](https://daringfireball.net/projects/markdown/) is commonly used to author text-heavy content like blog posts and documentation. Astro includes built-in support for Markdown files that can also include [frontmatter YAML](https://dev.to/paulasantamaria/introduction-to-yaml-125f) (or [TOML](https://toml.io)) to define custom properties such as a title, description, and tags.

Expand Down Expand Up @@ -170,8 +171,8 @@ You can customize these heading IDs with a [Markdown processor](#choosing-a-mark

Astro injects `id` attributes after your custom plugins have run, so any ID set by a plugin is preserved. If one of your custom plugins needs to access the IDs injected by Astro, you can import Astro's heading ids plugin and place it before any plugins that rely on it:

<Tabs syncKey="markdown-processor">
<TabItem label="Sätteri">
<MarkdownProcessorTabs>
<Fragment slot="satteri">
```js title="astro.config.mjs" ins={2-3, 9}
import { defineConfig } from 'astro/config';
import { satteri, satteriHeadingIdsPlugin } from '@astrojs/markdown-satteri';
Expand All @@ -188,8 +189,8 @@ Astro injects `id` attributes after your custom plugins have run, so any ID set
},
});
```
</TabItem>
<TabItem label="Unified">
</Fragment>
<Fragment slot="unified">
```js title="astro.config.mjs" ins={2-3, 9}
import { defineConfig } from 'astro/config';
import { unified, rehypeHeadingIds } from '@astrojs/markdown-remark';
Expand All @@ -206,8 +207,8 @@ Astro injects `id` attributes after your custom plugins have run, so any ID set
},
});
```
</TabItem>
</Tabs>
</Fragment>
</MarkdownProcessorTabs>

## Markdown Plugins

Expand Down
167 changes: 118 additions & 49 deletions src/content/docs/en/recipes/external-links.mdx
Original file line number Diff line number Diff line change
@@ -1,68 +1,137 @@
---
title: Add icons to external links
description: Learn how to install a rehype plugin to add icons to external links in your Markdown files.
description: Learn how to use a Markdown plugin to add icons to external links in your Markdown files.
i18nReady: true
type: recipe
---
import { Steps } from '@astrojs/starlight/components';
import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro';
import MarkdownProcessorTabs from '~/components/tabs/MarkdownProcessorTabs.astro';

Using a rehype plugin, you can identify and modify links in your Markdown files that point to external sites. This example adds icons to the end of each external link, so that visitors will know they are leaving your site.
Using a Markdown plugin, you can identify and modify links in your Markdown files that point to external sites. This example adds an icon to the end of each external link, so that visitors will know they are leaving your site.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious. Before we initially said rehype, which are the HTML plugins, and not remark, which are the Markdown plugins.

Should the new copyright be generic?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, remark and rehype do not run with the same context, but I think this is our generic term at the moment:

  • remark (unified) / mdast (Sätteri): plugins to control how Markdown files are parsed
  • rehype (unified) / hast (Sätteri): plugins to control the HTML output of Markdown files
  • unified plugins / Sätteri plugins: Markdown plugins

But I see what you mean; it might be oversimplified and could be confusing for some... Maybe "Markdown processor plugins" would be better to talk of both. But, I'm not sure what to use to distinguish between the two.
I mean, I don't think I've ever seen "HTML plugins" being used to refer to "rehype plugins". Even before Sätteri, "hast plugins" would have been more meaningful to me as rehype plugins work on a hast tree.

I was looking what others use:

  • Docusaurus uses "MDX plugins" to gather remark and rehype plugins
  • VitePress uses "Markdown extensions", but they use MarkdownIT so I'm not sure there is a concept like remark/rehype with it.

So, yeah, not sure what we should do here. 🤔

@ematipico ematipico Jul 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider also that I come from a place where I learnt about the different between remark and rehype very recently (during the Satteri migration), and unified was meant join them together. I don't know the level of knowledge of the audience of the guide, but consider that there people like me that don't know anything about this ecosystem (or very little).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, I'm totally biased here (I also use nlcst so... 😄 ). I think this is a good call and not only for the recipes, but the Markdown guide as well! I might create a Discord thread tomorrow for some brainstorming, unless I have a flash of inspiration overnight...

What I have so far... (spoiler: not much)

For remark/mdast and rehype/hast:

  • "Markdown AST plugins" and "HTML AST plugins"
  • "Markdown tree plugins" and "HTML tree plugins"
  • "MDAST plugins" and "HAST plugins" (well... seems accurate to me, but then it might be confusing because that's how Sätteri call them, not unified)
  • "remark and mdast plugins" and "rehype and hast plugins" 😅

As umbrella, for any plugin (this could include recma as well):

  • Markdown processor plugins (would that be as confusing as "Markdown plugins" given this includes rehype/hast?)
  • Processor plugins (as long as this is only used in Markdown context, this could work)
  • Syntax tree plugins
  • AST plugins

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I haven't forgotten that, and I'm not sure I had a "flash of inspiration" 😆 but, I'm trying a different approach: update the Markdown guide first to perhaps find a better idea for a name. (#14297, WIP!) We have at least one outdated section and we'll need to update "Markdown plugins" there too...

I probably won't have much time to dedicate to it tomorrow though, so I'll continue next week.


## Prerequisites

- An Astro project using Markdown for content pages.

## Recipe

<Steps>
1. Install both the [`rehype-external-links`](https://www.npmjs.com/package/rehype-external-links) plugin and [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark).
<MarkdownProcessorTabs>
<Fragment slot="satteri">
<Steps>
1. Install both [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) and [`satteri`](https://www.npmjs.com/package/satteri):

<PackageManagerTabs>
<Fragment slot="npm">
```shell
npm install rehype-external-links @astrojs/markdown-remark
<PackageManagerTabs>
<Fragment slot="npm">
```shell
npm install @astrojs/markdown-satteri satteri
```
</Fragment>
<Fragment slot="pnpm">
```shell
pnpm add @astrojs/markdown-satteri satteri
```
</Fragment>
<Fragment slot="yarn">
```shell
yarn add @astrojs/markdown-satteri satteri
```
</Fragment>
</PackageManagerTabs>

2. Create a [Sätteri `hast` plugin](https://satteri.bruits.org/docs/plugins/#hast-plugins) that adds an icon if the link starts with `http`:

```ts title="src/hast/hast-external-links.ts"
import { defineHastPlugin } from 'satteri';

export const hastExternalLinks = defineHastPlugin({
name: "hast-external-links",
element: {
filter: ["a"],
visit(node, context) {
if (node.properties.href?.startsWith("http")) {
context.appendChild(node, {
type: "element",
tagName: "span",
properties: { ariaHidden: "true" },
children: [
{
type: "text",
value: "🔗",
},
],
});
}
},
},
});
```
</Fragment>
<Fragment slot="pnpm">
```shell
pnpm add rehype-external-links @astrojs/markdown-remark

3. Configure the plugin in your `astro.config.mjs` file.

Import `satteri()` from `@astrojs/markdown-satteri` and define it as the Markdown processor to [define custom Sätteri plugins](/en/guides/markdown-content/#using-sätteri-plugins-and-features). Then, pass to `hastPlugins` an array containing your imported `hastExternalLinks` plugin.

```js title="astro.config.mjs"
import { satteri } from '@astrojs/markdown-satteri';
import { defineConfig } from 'astro/config';
import { hastExternalLinks } from './src/hast/hast-external-links';

export default defineConfig({
markdown: {
processor: satteri({
hastPlugins: [hastExternalLinks],
}),
},
});
```
</Fragment>
<Fragment slot="yarn">
```shell
yarn add rehype-external-links @astrojs/markdown-remark
</Steps>
</Fragment>
<Fragment slot="unified">
<Steps>
1. Install both the [`rehype-external-links`](https://www.npmjs.com/package/rehype-external-links) plugin and [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark).

<PackageManagerTabs>
<Fragment slot="npm">
```shell
npm install rehype-external-links @astrojs/markdown-remark
```
</Fragment>
<Fragment slot="pnpm">
```shell
pnpm add rehype-external-links @astrojs/markdown-remark
```
</Fragment>
<Fragment slot="yarn">
```shell
yarn add rehype-external-links @astrojs/markdown-remark
```
</Fragment>
</PackageManagerTabs>

2. Configure the plugin in your `astro.config.mjs` file.

Import `unified()` and define it as the Markdown processor to [support remark plugins](/en/guides/markdown-content/#using-remark-and-rehype-plugins). Then, pass to `rehypePlugins` an array containing your imported `rehypeExternalLinks` plugin and an options object with a `content` property. Set this property's `type` to `text` if you want to add plain text to the end of the link. To add HTML to the end of the link instead, set the property `type` to `raw`.

```js title="astro.config.mjs"
import { unified } from '@astrojs/markdown-remark';
import { defineConfig } from 'astro/config';
import rehypeExternalLinks from 'rehype-external-links';

export default defineConfig({
markdown: {
processor: unified({
rehypePlugins: [
[
rehypeExternalLinks,
{
content: { type: 'text', value: ' 🔗' }
}
],
]
}),
},
});
```
</Fragment>
</PackageManagerTabs>

2. Configure the plugin in your `astro.config.mjs` file.

Import `unified()` and define it as the Markdown processor to [support remark plugins](/en/guides/markdown-content/#using-remark-and-rehype-plugins). Then, pass to `rehypePlugins` an array containing your imported `rehypeExternalLinks` plugin and an options object with a `content` property. Set this property's `type` to `text` if you want to add plain text to the end of the link. To add HTML to the end of the link instead, set the property `type` to `raw`.

```js title="astro.config.mjs"
import { unified } from '@astrojs/markdown-remark';
import { defineConfig } from 'astro/config';
import rehypeExternalLinks from 'rehype-external-links';

export default defineConfig({
// ...
markdown: {
processor: unified({
rehypePlugins: [
[
rehypeExternalLinks,
{
content: { type: 'text', value: ' 🔗' }
}
],
]
}),
},
});
```

:::note
The value of the `content` property is [not represented in the accessibility tree](https://developer.mozilla.org/en-US/docs/Web/CSS/content#accessibility_concerns). As such, it's best to make clear that the link is external in the surrounding content, rather than relying on the icon alone.
:::
</Steps>
</Steps>
</Fragment>
</MarkdownProcessorTabs>
Loading
Loading