diff --git a/apps/www/src/content/docs/components/link/index.mdx b/apps/www/src/content/docs/components/link/index.mdx
index 84f79b75a..4af3f01c3 100644
--- a/apps/www/src/content/docs/components/link/index.mdx
+++ b/apps/www/src/content/docs/components/link/index.mdx
@@ -56,6 +56,16 @@ Additional style variations including underlined and external links.
+### Custom render
+
+Use the `render` prop to render as a custom element such as a router `Link`. All props (including `external`, `download`, and ARIA attributes) are forwarded to the provided element.
+
+```tsx
+import { Link as RouterLink } from "react-router-dom";
+
+}>About
+```
+
## Accessibility
The Link component follows accessibility best practices:
diff --git a/apps/www/src/content/docs/components/link/props.ts b/apps/www/src/content/docs/components/link/props.ts
index c26ef9779..e61cb7de6 100644
--- a/apps/www/src/content/docs/components/link/props.ts
+++ b/apps/www/src/content/docs/components/link/props.ts
@@ -1,6 +1,8 @@
+import type { ReactElement } from 'react';
+
export interface LinkProps {
- /** The URL that the link points to. (Required) */
- href: string;
+ /** The URL that the link points to. */
+ href?: string;
/** Whether the link should open in a new tab. */
external?: boolean;
@@ -8,6 +10,16 @@ export interface LinkProps {
/** Whether the link should be downloadable or a string for the filename. */
download?: boolean | string;
+ /**
+ * Custom element used to render the Link.
+ *
+ * All props are forwarded to the specified element. Useful for integrating
+ * with router libraries (e.g. ``, ``).
+ *
+ * @default ""
+ */
+ render?: ReactElement;
+
/** Additional CSS class names. */
className?: string;
}
diff --git a/packages/raystack/components/link/__tests__/link.test.tsx b/packages/raystack/components/link/__tests__/link.test.tsx
index 5630fd492..bb73c2763 100644
--- a/packages/raystack/components/link/__tests__/link.test.tsx
+++ b/packages/raystack/components/link/__tests__/link.test.tsx
@@ -77,6 +77,47 @@ describe('Link', () => {
});
});
+ describe('Custom render', () => {
+ it('renders as the element provided via render prop', () => {
+ render(
+ }>
+ Custom
+
+ );
+ const link = screen.getByRole('link');
+ expect(link.tagName).toBe('BUTTON');
+ expect(link).toHaveAttribute('type', 'button');
+ });
+
+ it('forwards Link props to the custom rendered element', () => {
+ render(
+ }
+ >
+ External
+
+ );
+ const link = screen.getByRole('link');
+ expect(link).toHaveAttribute('href', 'https://example.com');
+ expect(link).toHaveAttribute('target', '_blank');
+ expect(link).toHaveAttribute('rel', 'noopener noreferrer');
+ expect(link).toHaveAttribute('data-custom', 'yes');
+ });
+
+ it('preserves the link className on the custom rendered element', () => {
+ render(
+ }>
+ Custom
+
+ );
+ const link = screen.getByRole('link');
+ expect(link.tagName).toBe('DIV');
+ expect(link).toHaveClass(styles.link);
+ });
+ });
+
describe('HTML Attributes', () => {
it('supports title attribute', () => {
render(
diff --git a/packages/raystack/components/link/link.tsx b/packages/raystack/components/link/link.tsx
index 811a7e4a1..bc64975a8 100644
--- a/packages/raystack/components/link/link.tsx
+++ b/packages/raystack/components/link/link.tsx
@@ -1,10 +1,11 @@
+import { useRender } from '@base-ui/react';
import { cx } from 'class-variance-authority';
-import { ComponentProps } from 'react';
import { Text, type TextBaseProps } from '../text';
import styles from './link.module.css';
-export interface LinkProps extends TextBaseProps, ComponentProps<'a'> {
- href: string;
+export interface LinkProps
+ extends TextBaseProps,
+ useRender.ComponentProps<'a'> {
external?: boolean;
download?: boolean | string;
}
@@ -16,6 +17,7 @@ export function Link({
size = 'small',
external,
download,
+ render = ,
...props
}: LinkProps) {
const externalProps = external
@@ -42,7 +44,7 @@ export function Link({
{...externalProps}
{...downloadProps}
{...props}
- render={}
+ render={render}
>
{children}