diff --git a/.changeset/silent-dots-run.md b/.changeset/silent-dots-run.md
new file mode 100644
index 00000000000..0fce0295c4a
--- /dev/null
+++ b/.changeset/silent-dots-run.md
@@ -0,0 +1,5 @@
+---
+"@salt-ds/core": minor
+---
+
+Added `render` prop to `Link`.
diff --git a/packages/core/src/__tests__/__e2e__/link/Link.cy.tsx b/packages/core/src/__tests__/__e2e__/link/Link.cy.tsx
index d200510d44d..6d8711794bb 100644
--- a/packages/core/src/__tests__/__e2e__/link/Link.cy.tsx
+++ b/packages/core/src/__tests__/__e2e__/link/Link.cy.tsx
@@ -42,4 +42,40 @@ describe("GIVEN a link", () => {
cy.findByTestId(/TearOutIcon/i).should("not.exist");
});
+
+ it("WHEN `render` is passed a render function, THEN should call `render` to create the element", () => {
+ const testId = "link-testid";
+
+ const mockRender = cy
+ .stub()
+ .as("render")
+ .returns(
+
+ Action
+ ,
+ );
+
+ cy.mount();
+
+ cy.findByTestId(testId).should("exist");
+
+ cy.get("@render").should("have.been.calledWithMatch", {
+ className: Cypress.sinon.match.string,
+ children: Cypress.sinon.match.any,
+ });
+ });
+
+ it("WHEN `render` is given a JSX element, THEN should merge the props and render the JSX element", () => {
+ const testId = "link-testid";
+
+ const mockRender = (
+
+ Action
+
+ );
+
+ cy.mount();
+
+ cy.findByTestId(testId).should("exist");
+ });
});
diff --git a/packages/core/src/link/Link.tsx b/packages/core/src/link/Link.tsx
index 0a86426e100..f2e3a27cef5 100644
--- a/packages/core/src/link/Link.tsx
+++ b/packages/core/src/link/Link.tsx
@@ -4,9 +4,11 @@ import { useWindow } from "@salt-ds/window";
import { clsx } from "clsx";
import { type ComponentType, type ReactElement, forwardRef } from "react";
import { useIcon } from "../semantic-icon-provider";
-import { Text, type TextProps } from "../text";
+import type { TextProps } from "../text";
import { makePrefixer } from "../utils";
+import type { RenderPropsType } from "../utils";
import linkCss from "./Link.css";
+import { LinkAction } from "./LinkAction";
const withBaseName = makePrefixer("saltLink");
@@ -18,6 +20,10 @@ const withBaseName = makePrefixer("saltLink");
*/
export interface LinkProps extends Omit, "as" | "disabled"> {
IconComponent?: ComponentType | null;
+ /**
+ * Render prop to enable customisation of link element.
+ */
+ render?: RenderPropsType["render"];
}
export const Link = forwardRef(function Link(
@@ -29,6 +35,7 @@ export const Link = forwardRef(function Link(
color: colorProp,
variant,
target = "_self",
+ render,
...rest
},
ref,
@@ -46,13 +53,14 @@ export const Link = forwardRef(function Link(
IconComponent === undefined ? ExternalIcon : IconComponent;
return (
-
{children}
@@ -64,6 +72,6 @@ export const Link = forwardRef(function Link(
External
>
)}
-
+
);
});
diff --git a/packages/core/src/link/LinkAction.tsx b/packages/core/src/link/LinkAction.tsx
new file mode 100644
index 00000000000..15698db5045
--- /dev/null
+++ b/packages/core/src/link/LinkAction.tsx
@@ -0,0 +1,15 @@
+import type { ComponentPropsWithoutRef } from "react";
+import { Text } from "../text";
+import { renderProps } from "../utils";
+
+interface LinkActionProps extends ComponentPropsWithoutRef {}
+
+export function LinkAction(props: LinkActionProps) {
+ const { render, ...rest } = props;
+
+ if (render) {
+ return renderProps("a", props);
+ }
+
+ return ;
+}
diff --git a/packages/core/stories/link/link.stories.tsx b/packages/core/stories/link/link.stories.tsx
index b61b8c54666..903c69fcf47 100644
--- a/packages/core/stories/link/link.stories.tsx
+++ b/packages/core/stories/link/link.stories.tsx
@@ -101,3 +101,22 @@ export const Truncation: StoryFn = () => {
//
// );
// };
+
+const CustomLinkImplementation = (props: any) => (
+
+ Your own Link implementation
+
+);
+
+export const RenderElement: StoryFn = () => {
+ return } />;
+};
+
+export const RenderProp: StoryFn = () => {
+ return (
+ }
+ />
+ );
+};
diff --git a/site/docs/components/link/examples.mdx b/site/docs/components/link/examples.mdx
index a02f7c3c6e6..62c228db9c7 100644
--- a/site/docs/components/link/examples.mdx
+++ b/site/docs/components/link/examples.mdx
@@ -66,4 +66,20 @@ The default variant is `primary`.
+
+
+## Render prop - element
+
+Using the `render` prop, you can customize the element rendered by the Link. Props defined on the JSX element will be merged with props from the Link.
+
+
+
+
+
+## Render prop - callback
+
+The `render` prop can also accept a function. This approach allows more control over how props are merged, allowing for more precise customization of the component's behavior.
+
+
+
diff --git a/site/src/examples/link/RenderElement.tsx b/site/src/examples/link/RenderElement.tsx
new file mode 100644
index 00000000000..790d610ff3d
--- /dev/null
+++ b/site/src/examples/link/RenderElement.tsx
@@ -0,0 +1,19 @@
+import { Link, Text } from "@salt-ds/core";
+import type { ReactElement } from "react";
+import styles from "./index.module.css";
+
+const CustomLinkImplementation = (props: any) => (
+
+ Your own Link implementation
+
+);
+
+export const RenderElement = (): ReactElement => {
+ return (
+ }
+ />
+ );
+};
diff --git a/site/src/examples/link/RenderProp.tsx b/site/src/examples/link/RenderProp.tsx
new file mode 100644
index 00000000000..48d68017c80
--- /dev/null
+++ b/site/src/examples/link/RenderProp.tsx
@@ -0,0 +1,19 @@
+import { Link, Text } from "@salt-ds/core";
+import type { ReactElement } from "react";
+import styles from "./index.module.css";
+
+const CustomLinkImplementation = (props: any) => (
+
+ Your own Link implementation
+
+);
+
+export const RenderProp = (): ReactElement => {
+ return (
+ }
+ />
+ );
+};
diff --git a/site/src/examples/link/index.ts b/site/src/examples/link/index.ts
index 8a3cedda73b..cdf3700890c 100644
--- a/site/src/examples/link/index.ts
+++ b/site/src/examples/link/index.ts
@@ -3,3 +3,5 @@ export * from "./OpenInANewTab";
export * from "./Variant";
export * from "./Color";
export * from "./Visited";
+export * from "./RenderElement";
+export * from "./RenderProp";