diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/components/link/link.component.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/link/link.component.ts
new file mode 100644
index 0000000000..c49258e30f
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/link/link.component.ts
@@ -0,0 +1,15 @@
+import type { UfmToken } from '../../plugins/marked-ufm.plugin.js';
+import { UmbUfmComponentBase } from '../ufm-component-base.js';
+
+import './link.element.js';
+
+export class UmbUfmLinkComponent extends UmbUfmComponentBase {
+ render(token: UfmToken) {
+ if (!token.text) return;
+
+ const attributes = super.getAttributes(token.text);
+ return ``;
+ }
+}
+
+export { UmbUfmLinkComponent as api };
diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/components/link/link.element.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/link/link.element.ts
new file mode 100644
index 0000000000..b53b276b00
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/link/link.element.ts
@@ -0,0 +1,84 @@
+import { UmbUfmElementBase } from '../ufm-element-base.js';
+import { UMB_UFM_RENDER_CONTEXT } from '../ufm-render/ufm-render.context.js';
+import { customElement, property } from '@umbraco-cms/backoffice/external/lit';
+import { UmbDocumentItemRepository, UMB_DOCUMENT_ENTITY_TYPE } from '@umbraco-cms/backoffice/document';
+import { UmbMediaItemRepository, UMB_MEDIA_ENTITY_TYPE } from '@umbraco-cms/backoffice/media';
+import type { UmbLinkPickerLink } from '@umbraco-cms/backoffice/multi-url-picker';
+
+const elementName = 'ufm-link';
+
+@customElement(elementName)
+export class UmbUfmLinkElement extends UmbUfmElementBase {
+ @property()
+ alias?: string;
+
+ #documentRepository?: UmbDocumentItemRepository;
+ #mediaRepository?: UmbMediaItemRepository;
+
+ constructor() {
+ super();
+
+ this.consumeContext(UMB_UFM_RENDER_CONTEXT, (context) => {
+ this.observe(
+ context.value,
+ async (value) => {
+ const temp =
+ this.alias && typeof value === 'object'
+ ? (value as Record)[this.alias]
+ : (value as unknown);
+
+ if (!temp) return;
+
+ const items = Array.isArray(temp) ? temp : [temp];
+ const names = await Promise.all(items.map(async (item) => await this.#getName(item)));
+ this.value = names.filter((x) => x).join(', ');
+ },
+ 'observeValue',
+ );
+ });
+ }
+
+ async #getName(item?: unknown) {
+ const link = item as UmbLinkPickerLink;
+
+ if (link.name) {
+ return link.name;
+ }
+
+ const entityType = link.type;
+ const unique = link.unique;
+
+ if (unique) {
+ const repository = this.#getRepository(entityType);
+ if (repository) {
+ const { data } = await repository.requestItems([unique]);
+ if (Array.isArray(data) && data.length > 0) {
+ return data.map((item) => item.name).join(', ');
+ }
+ }
+ }
+
+ return '';
+ }
+
+ #getRepository(entityType?: string | null) {
+ switch (entityType) {
+ case UMB_MEDIA_ENTITY_TYPE:
+ if (!this.#mediaRepository) this.#mediaRepository = new UmbMediaItemRepository(this);
+ return this.#mediaRepository;
+
+ case UMB_DOCUMENT_ENTITY_TYPE:
+ default:
+ if (!this.#documentRepository) this.#documentRepository = new UmbDocumentItemRepository(this);
+ return this.#documentRepository;
+ }
+ }
+}
+
+export { UmbUfmLinkElement as element };
+
+declare global {
+ interface HTMLElementTagNameMap {
+ [elementName]: UmbUfmLinkElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/components/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/manifests.ts
index b9c7f5dc6f..c247af5a31 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/ufm/components/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/manifests.ts
@@ -22,4 +22,11 @@ export const manifests: Array = [
api: () => import('./content-name/content-name.component.js'),
meta: { alias: 'umbContentName', marker: '~' },
},
+ {
+ type: 'ufmComponent',
+ alias: 'Umb.Markdown.Link',
+ name: 'Link UFM Component',
+ api: () => import('./link/link.component.js'),
+ meta: { alias: 'umbLink' },
+ },
];