diff --git a/docs/src/content/docs/components/asides.mdx b/docs/src/content/docs/components/asides.mdx
index a28375b0d60..9ccf1ff189c 100644
--- a/docs/src/content/docs/components/asides.mdx
+++ b/docs/src/content/docs/components/asides.mdx
@@ -49,6 +49,10 @@ Other content is also supported in asides.
+
+
````
@@ -73,6 +77,10 @@ Other content is also supported in asides.
{% aside type="danger" %}
Do not give your password to anyone.
{% /aside %}
+
+{% aside icon="heart" %}
+Aside with a custom icon. The icon must be part of starlight's icon list.
+{% /aside %}
````
@@ -95,6 +103,10 @@ Do not give your password to anyone.
+
+
diff --git a/docs/src/content/docs/guides/authoring-content.mdx b/docs/src/content/docs/guides/authoring-content.mdx
index 819e4f50016..98fccc69d10 100644
--- a/docs/src/content/docs/guides/authoring-content.mdx
+++ b/docs/src/content/docs/guides/authoring-content.mdx
@@ -151,6 +151,20 @@ Astro helps you build faster websites with [“Islands Architecture”](https://
:::
```
+### Custom aside icons
+
+You can specify a custom icon for the aside in curly brackets following the aside type/title, e.g. `:::tip[Did you know?]{icon="heart"}`.
+
+:::tip[Did you know?]{icon="heart"}
+Astro helps you build faster websites with [“Islands Architecture”](https://docs.astro.build/en/concepts/islands/).
+:::
+
+```md
+:::tip[Did you know?]{icon="heart"}
+Astro helps you build faster websites with [“Islands Architecture”](https://docs.astro.build/en/concepts/islands/).
+:::
+```
+
### More aside types
Caution and danger asides are helpful for drawing a user’s attention to details that may trip them up.
diff --git a/packages/starlight/__tests__/remark-rehype/asides.test.ts b/packages/starlight/__tests__/remark-rehype/asides.test.ts
index cda21bb7b5f..d72914a81bf 100644
--- a/packages/starlight/__tests__/remark-rehype/asides.test.ts
+++ b/packages/starlight/__tests__/remark-rehype/asides.test.ts
@@ -103,6 +103,29 @@ Some text
);
});
+describe('custom icons', () => {
+ test.each(['note', 'tip', 'caution', 'danger'])('%s with custom label', async (type) => {
+ const res = await processor.render(`
+:::${type}{icon="heart"}
+Some text
+:::
+ `);
+ expect(res.code).includes(
+ 'M20.16 5A6.29 6.29 0 0 0 12 4.36a6.27 6.27 0 0 0-8.16 9.48l6.21 6.22a2.78 2.78 0 0 0 3.9 0l6.21-6.22a6.27 6.27 0 0 0 0-8.84m-1.41 7.46-6.21 6.21a.76.76 0 0 1-1.08 0l-6.21-6.24a4.29 4.29 0 0 1 0-6 4.27 4.27 0 0 1 6 0 1 1 0 0 0 1.42 0 4.27 4.27 0 0 1 6 0 4.29 4.29 0 0 1 .08 6Z'
+ );
+
+ expect(async () =>
+ processor.render(`
+:::${type}{icon="invalid-icon-name"}
+Some text
+:::
+ `)
+ ).rejects.toThrowError(
+ 'Failed to parse Markdown file "undefined":\nIcon name should be part of Starlight\'s icon list.'
+ );
+ });
+});
+
test('ignores unknown directive variants', async () => {
const res = await processor.render(`
:::unknown
diff --git a/packages/starlight/integrations/asides.ts b/packages/starlight/integrations/asides.ts
index 00ca11a603c..bbe1c9b03fb 100644
--- a/packages/starlight/integrations/asides.ts
+++ b/packages/starlight/integrations/asides.ts
@@ -17,6 +17,10 @@ import { visit } from 'unist-util-visit';
import type { StarlightConfig } from '../types';
import type { createTranslationSystemFromFs } from '../utils/translations-fs';
import { pathToLocale } from './shared/pathToLocale';
+import { Icons } from '../components/Icons';
+import { fromHtml } from 'hast-util-from-html';
+import type { Element } from 'hast';
+import { AstroError } from 'astro/errors';
interface AsidesOptions {
starlightConfig: { locales: StarlightConfig['locales'] };
@@ -160,6 +164,7 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
return;
}
const variant = node.name;
+ const attributes = node.attributes;
if (!isAsideVariant(variant)) return;
// remark-directive converts a container’s “label” to a paragraph added as the head of its
@@ -181,6 +186,19 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
node.children.splice(0, 1);
}
+ let iconPath = iconPaths[variant];
+ if (attributes) {
+ if (attributes['icon']) {
+ const iconName = attributes['icon'] as keyof typeof Icons;
+ if (!Object.keys(Icons).includes(iconName)) {
+ throw new AstroError("Icon name should be part of Starlight's icon list.");
+ }
+ const { properties } = fromHtml(Icons[iconName], { fragment: true })
+ .children[0] as Element;
+ iconPath = [s('path', properties)];
+ }
+ }
+
const aside = h(
'aside',
{
@@ -198,7 +216,7 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
fill: 'currentColor',
class: 'starlight-aside__icon',
},
- iconPaths[variant]
+ iconPath
),
...titleNode,
]),
diff --git a/packages/starlight/user-components/Aside.astro b/packages/starlight/user-components/Aside.astro
index 471f2fa0ae1..cdb448d511f 100644
--- a/packages/starlight/user-components/Aside.astro
+++ b/packages/starlight/user-components/Aside.astro
@@ -12,7 +12,7 @@ interface Props {
title?: string;
}
-let { type = 'note', title } = Astro.props;
+let { type = 'note', title, icon } = Astro.props;
if (!asideVariants.includes(type)) {
throw new AstroError(
@@ -30,7 +30,7 @@ if (!title) {