diff --git a/docs/how-to-guides/customize-related-items.md b/docs/how-to-guides/customize-related-items.md
new file mode 100644
index 000000000..696f980c7
--- /dev/null
+++ b/docs/how-to-guides/customize-related-items.md
@@ -0,0 +1,50 @@
+---
+myst:
+ html_meta:
+ "description": "How to hide or replace the related items list shown below content in Plone Aurora"
+ "property=og:description": "How to hide or replace the related items list shown below content in Plone Aurora"
+ "property=og:title": "Customize the related items"
+ "keywords": "Plone Aurora, related items, slot, belowContent"
+---
+
+# Customize the related items
+
+Plone Aurora renders a content item's related items below the content as links.
+The list comes from a slot component registered for the `belowContent` slot.
+This guide shows you how to hide or replace it.
+
+## Hide the related items
+
+Set `showRelatedItems` to `false` in your add-on configuration to hide the list on every page.
+
+```ts
+import type { ConfigType } from '@plone/registry';
+
+export default function install(config: ConfigType) {
+ config.settings.showRelatedItems = false;
+ return config;
+}
+```
+
+The slot renders the list whenever an item has `relatedItems`, unless `showRelatedItems` is `false`.
+
+## Replace the related items component
+
+Register your own component for the `belowContent` slot under the same name to override the default.
+
+```ts
+import type { ConfigType } from '@plone/registry';
+import MyRelatedItems from './MyRelatedItems';
+
+export default function install(config: ConfigType) {
+ config.registerSlotComponent({
+ name: 'RelatedItems',
+ slot: 'belowContent',
+ component: MyRelatedItems,
+ });
+ return config;
+}
+```
+
+Your component receives the slot props, including `content`, from which you can read `content.relatedItems`.
+To learn how slots work in general, refer to {doc}`register-slots`.
diff --git a/docs/how-to-guides/index.md b/docs/how-to-guides/index.md
index b42b76e4c..fcb1fbcf3 100644
--- a/docs/how-to-guides/index.md
+++ b/docs/how-to-guides/index.md
@@ -30,4 +30,5 @@ icons
configure-plate-code-block-languages
bind-metadata-fields-to-plate-text-blocks
custom-content-types
+customize-related-items
```
diff --git a/packages/layout/config/slots.ts b/packages/layout/config/slots.ts
index ab747d69c..7780bc89e 100644
--- a/packages/layout/config/slots.ts
+++ b/packages/layout/config/slots.ts
@@ -9,6 +9,7 @@ import HeaderTools from '../slots/Tools';
import ContentArea from '../slots/ContentArea';
import MainFooter from '../slots/MainFooter/MainFooter';
import Breadcrumbs from '../slots/Breadcrumbs';
+import RelatedItems from '../slots/RelatedItems/RelatedItems';
import { NotContentTypeCondition } from '../helpers';
export default function install(config: ConfigType) {
@@ -83,5 +84,12 @@ export default function install(config: ConfigType) {
component: MainFooter,
});
+ // Related Items
+ config.registerSlotComponent({
+ name: 'RelatedItems',
+ slot: 'belowContent',
+ component: RelatedItems,
+ });
+
return config;
}
diff --git a/packages/layout/locales/de/common.json b/packages/layout/locales/de/common.json
index a9282556f..0d582f243 100644
--- a/packages/layout/locales/de/common.json
+++ b/packages/layout/locales/de/common.json
@@ -17,6 +17,9 @@
"toolbar": {
"label": "Werkzeugleiste"
},
+ "relatedItems": {
+ "label": "Verwandte Inhalte"
+ },
"views": {
"link": {
"linkLabel": "Die Linkadresse lautet:",
diff --git a/packages/layout/locales/en/common.json b/packages/layout/locales/en/common.json
index 015eedda6..334c91731 100644
--- a/packages/layout/locales/en/common.json
+++ b/packages/layout/locales/en/common.json
@@ -17,6 +17,9 @@
"toolbar": {
"label": "Toolbar"
},
+ "relatedItems": {
+ "label": "Related items"
+ },
"views": {
"link": {
"linkLabel": "The link address is:",
diff --git a/packages/layout/locales/it/common.json b/packages/layout/locales/it/common.json
index 6a5bf7a24..62040ccb8 100644
--- a/packages/layout/locales/it/common.json
+++ b/packages/layout/locales/it/common.json
@@ -17,6 +17,9 @@
"toolbar": {
"label": "Barra degli strumenti"
},
+ "relatedItems": {
+ "label": "Contenuti correlati"
+ },
"views": {
"link": {
"linkLabel": "L'indirizzo del link รจ:",
diff --git a/packages/layout/news/64.feature b/packages/layout/news/64.feature
new file mode 100644
index 000000000..d6dc3b1c5
--- /dev/null
+++ b/packages/layout/news/64.feature
@@ -0,0 +1 @@
+Added a Related Items slot that renders a content item's related items as links below the content. @Cabonie
diff --git a/packages/layout/slots/RelatedItems/RelatedItems.module.css b/packages/layout/slots/RelatedItems/RelatedItems.module.css
new file mode 100644
index 000000000..b87d129b1
--- /dev/null
+++ b/packages/layout/slots/RelatedItems/RelatedItems.module.css
@@ -0,0 +1,10 @@
+@layer custom {
+ .relatedItems {
+ display: flex;
+ flex-direction: column;
+ padding: 0;
+ margin-top: 24px;
+ gap: 8px;
+ list-style: none;
+ }
+}
diff --git a/packages/layout/slots/RelatedItems/RelatedItems.test.tsx b/packages/layout/slots/RelatedItems/RelatedItems.test.tsx
new file mode 100644
index 000000000..becb99167
--- /dev/null
+++ b/packages/layout/slots/RelatedItems/RelatedItems.test.tsx
@@ -0,0 +1,40 @@
+import { render, screen } from '@testing-library/react';
+import { MemoryRouter } from 'react-router';
+import { RouterProvider } from 'react-aria-components';
+import RelatedItems from './RelatedItems';
+
+type Item = { '@id': string; title: string };
+
+const renderRelatedItems = (relatedItems: Item[]) =>
+ render(
+
+ undefined}>
+
+
+ ,
+ );
+
+describe('RelatedItems slot', () => {
+ it('renders nothing when there are no related items', () => {
+ const { container } = renderRelatedItems([]);
+ expect(container).toBeEmptyDOMElement();
+ });
+
+ it('renders a link for each related item', () => {
+ renderRelatedItems([
+ { '@id': '/news/one', title: 'News One' },
+ { '@id': '/events/two', title: 'Event Two' },
+ ]);
+ expect(screen.getByRole('link', { name: 'News One' })).toHaveAttribute(
+ 'href',
+ '/news/one',
+ );
+ expect(screen.getByRole('link', { name: 'Event Two' })).toHaveAttribute(
+ 'href',
+ '/events/two',
+ );
+ });
+});
diff --git a/packages/layout/slots/RelatedItems/RelatedItems.tsx b/packages/layout/slots/RelatedItems/RelatedItems.tsx
new file mode 100644
index 000000000..17d6d2c4f
--- /dev/null
+++ b/packages/layout/slots/RelatedItems/RelatedItems.tsx
@@ -0,0 +1,34 @@
+import { useTranslation } from 'react-i18next';
+import { Link } from '@plone/components';
+import config from '@plone/registry';
+import type { SlotComponentProps } from '../SlotRenderer';
+import SectionWrapper from '../../components/SectionWrapper/SectionWrapper';
+import styles from './RelatedItems.module.css';
+
+const RelatedItems = (props: SlotComponentProps) => {
+ const { t } = useTranslation();
+ const items = props.content.relatedItems ?? [];
+
+ const showRelatedItems = config.settings.showRelatedItems !== false;
+ if (!showRelatedItems || items.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+ {items.map((item) => (
+ -
+ {item.title}
+
+ ))}
+
+
+ );
+};
+
+export default RelatedItems;