diff --git a/src/app/(home)/gruppe/[slug]/page.tsx b/src/app/(home)/gruppe/[slug]/page.tsx
index 4751e40..6341b60 100644
--- a/src/app/(home)/gruppe/[slug]/page.tsx
+++ b/src/app/(home)/gruppe/[slug]/page.tsx
@@ -14,7 +14,7 @@ import { getPhoto } from '@/utils/dto/gallery'
import { isAuthenticated } from '@/utils/auth'
import { AdminMenu } from '@/components/AdminMenu/AdminMenu'
import { GroupEvents } from '@/compositions/GroupEvents/GroupEvents'
-import { RichText } from '@payloadcms/richtext-lexical/react'
+import { RichText } from '@/components/Text/RichText'
import { RefreshRouteOnSave } from '@/components/RefreshRouteOnSave/RefreshRouteOnSave'
import { draftMode } from 'next/headers'
diff --git a/src/app/(home)/spenden/[id]/page.tsx b/src/app/(home)/spenden/[id]/page.tsx
index 2dbd6e7..7c89189 100644
--- a/src/app/(home)/spenden/[id]/page.tsx
+++ b/src/app/(home)/spenden/[id]/page.tsx
@@ -1,7 +1,7 @@
import { PageHeader } from '@/compositions/PageHeader/PageHeader'
import { fetchDonationForm } from '@/fetch/donationform'
import { notFound } from 'next/navigation'
-import { RichText } from '@payloadcms/richtext-lexical/react'
+import { RichText } from '@/components/Text/RichText'
import { Container } from '@/components/Container/Container'
import { Section } from '@/components/Section/Section'
import styles from '@/components/DonationForm/styles.module.scss'
diff --git a/src/components/Classifieds/Ad.tsx b/src/components/Classifieds/Ad.tsx
index c4d1a22..57db730 100644
--- a/src/components/Classifieds/Ad.tsx
+++ b/src/components/Classifieds/Ad.tsx
@@ -1,10 +1,9 @@
"use client"
-import classNames from 'classnames'
import styles from '@/components/Classifieds/styles.module.scss'
import { useState } from 'react'
import { SerializedEditorState } from 'lexical'
-import { RichText } from '@payloadcms/richtext-lexical/react'
+import { RichText } from '@/components/Text/RichText'
type AdProps = {
text: SerializedEditorState,
diff --git a/src/components/Text/HTMLText.tsx b/src/components/Text/HTMLText.tsx
index 1a5e33e..f50e4f8 100644
--- a/src/components/Text/HTMLText.tsx
+++ b/src/components/Text/HTMLText.tsx
@@ -1,7 +1,7 @@
import styles from "./html.module.scss"
import classNames from 'classnames'
-import { RichText } from '@payloadcms/richtext-lexical/react'
import { SerializedEditorState, SerializedLexicalNode } from 'lexical'
+import { RichText } from './RichText'
type HTMLTextProps = {
width: "1/2" | "3/4",
diff --git a/src/components/Text/RichText.tsx b/src/components/Text/RichText.tsx
new file mode 100644
index 0000000..cc63591
--- /dev/null
+++ b/src/components/Text/RichText.tsx
@@ -0,0 +1,16 @@
+import { RichText as PayloadRichText } from '@payloadcms/richtext-lexical/react'
+import { SerializedEditorState } from 'lexical'
+import { jsxConverters } from './converters'
+
+
+type RichTextProps = {
+ data: SerializedEditorState
+}
+
+// Thin wrapper around Payload's RichText that always wires up our custom JSX
+// converters (e.g. rendering links marked as "Button" via the Button component).
+// Use this everywhere instead of importing RichText directly from Payload, so
+// the converters can never be forgotten.
+export const RichText = ({ data }: RichTextProps) => (
+
+)
diff --git a/src/components/Text/converters.tsx b/src/components/Text/converters.tsx
new file mode 100644
index 0000000..054caac
--- /dev/null
+++ b/src/components/Text/converters.tsx
@@ -0,0 +1,50 @@
+import type { JSXConvertersFunction } from '@payloadcms/richtext-lexical/react'
+import type { SerializedLinkNode } from '@payloadcms/richtext-lexical'
+import { Button } from '@/components/Button/Button'
+
+// Lexical link nodes carry their editable metadata (url, newTab, linkType, ...)
+// in `fields`. We extend that shape with the custom `appearance` select that is
+// added to LinkFeature in `payload.config.ts`, so editors can mark a link as a
+// call-to-action button.
+type LinkFields = SerializedLinkNode['fields'] & {
+ appearance?: 'link' | 'button'
+}
+
+// Custom JSX converters passed to wherever rich text is rendered.
+// Only the `link` converter is overridden — every other node keeps Payload's
+// default rendering via the spread of `defaultConverters`.
+export const jsxConverters: JSXConvertersFunction = ({ defaultConverters }) => ({
+ ...defaultConverters,
+ link: (args) => {
+ const { node, nodesToJSX } = args
+ const fields = node.fields as LinkFields
+
+ // Normal link → delegate to Payload's built-in link converter.
+ // The default converter from the package is a function, but the type
+ // also allows a static ReactNode, so we narrow before calling.
+ if (fields?.appearance !== 'button') {
+ const defaultLink = defaultConverters.link
+ return typeof defaultLink === 'function' ? defaultLink(args) : defaultLink
+ }
+
+ // Button link → resolve href the same way Payload's default converter does:
+ // internal links point at the related doc's slug, custom links use `url`.
+ const href =
+ fields.linkType === 'internal' && typeof fields.doc?.value === 'object'
+ ? `/${(fields.doc.value as { slug?: string }).slug ?? ''}`
+ : (fields.url ?? '#')
+
+ // Render the existing Button component. Schema and size are intentionally
+ // hardcoded — editors only choose link vs. button, not the styling.
+ return (
+
+ )
+ },
+})
diff --git a/src/payload.config.ts b/src/payload.config.ts
index 2ca8897..bd265e5 100644
--- a/src/payload.config.ts
+++ b/src/payload.config.ts
@@ -138,7 +138,21 @@ export default buildConfig({
HeadingFeature({ enabledHeadingSizes: ["h3","h4","h5"]}),
AlignFeature(),
UnorderedListFeature(),
- LinkFeature(),
+ LinkFeature({
+ fields: ({ defaultFields }) => [
+ ...defaultFields,
+ {
+ name: 'appearance',
+ type: 'select',
+ defaultValue: 'link',
+ label: 'Darstellung',
+ options: [
+ { label: 'Link', value: 'link' },
+ { label: 'Button', value: 'button' },
+ ],
+ },
+ ],
+ }),
ParagraphFeature(),
InlineToolbarFeature(),
FixedToolbarFeature()