Base Components
Link Button
Buttons that seamlessly integrate with text content.
Installation
Install the following dependencies:
npm install @radix-ui/react-slotCreate a link-button.tsx file and paste the following code into it.
// CoreUI LinkButton v0.0.0import * as React from 'react';import { Slot } from '@radix-ui/react-slot';import { PolymorphicComponentProps } from '@/utils/polymorphic';import { recursiveCloneChildren } from '@/utils/recursive-clone-children';import { tv, type VariantProps } from '@/utils/tv';const LINK_BUTTON_ROOT_NAME = 'LinkButtonRoot';const LINK_BUTTON_ICON_NAME = 'LinkButtonIcon';export const linkButtonVariants = tv({ slots: { root: [ // base 'group inline-flex items-center justify-center whitespace-nowrap outline-none', 'transition duration-200 ease-out', 'underline decoration-transparent underline-offset-[3px]', // hover 'hover:decoration-current', // focus 'focus:outline-none focus-visible:underline', // disabled 'disabled:text-text-disabled-300 disabled:pointer-events-none disabled:no-underline', ], icon: 'shrink-0', }, variants: { variant: { gray: { root: [ // base 'text-text-sub-600', // focus 'focus-visible:text-text-strong-950', ], }, black: { root: 'text-text-strong-950', }, primary: { root: [ // base 'text-primary-base', // hover 'hover:text-primary-darker', ], }, error: { root: [ // base 'text-error-base', // hover 'hover:text-red-700', ], }, modifiable: {}, }, size: { medium: { root: 'text-label-sm h-5 gap-1', icon: 'size-5', }, small: { root: 'text-label-xs h-4 gap-1', icon: 'size-4', }, }, underline: { true: { root: 'decoration-current', }, }, }, defaultVariants: { variant: 'gray', size: 'medium', },});type LinkButtonSharedProps = VariantProps<typeof linkButtonVariants>;type LinkButtonProps = VariantProps<typeof linkButtonVariants> & React.ButtonHTMLAttributes<HTMLButtonElement> & { asChild?: boolean; };const LinkButtonRoot = React.forwardRef<HTMLButtonElement, LinkButtonProps>( ( { asChild, children, variant, size, underline, className, ...rest }, forwardedRef, ) => { const uniqueId = React.useId(); const Component = asChild ? Slot : 'button'; const { root } = linkButtonVariants({ variant, size, underline }); const sharedProps: LinkButtonSharedProps = { variant, size, }; const extendedChildren = recursiveCloneChildren( children as React.ReactElement[], sharedProps, [LINK_BUTTON_ICON_NAME], uniqueId, asChild, ); return ( <Component ref={forwardedRef} className={root({ class: className })} {...rest} > {extendedChildren} </Component> ); },);LinkButtonRoot.displayName = LINK_BUTTON_ROOT_NAME;function LinkButtonIcon<T extends React.ElementType>({ className, variant, size, as, ...rest}: PolymorphicComponentProps<T, LinkButtonSharedProps>) { const Component = as || 'div'; const { icon } = linkButtonVariants({ variant, size }); return <Component className={icon({ class: className })} {...rest} />;}LinkButtonIcon.displayName = LINK_BUTTON_ICON_NAME;export { LinkButtonRoot as Root, LinkButtonIcon as Icon };Examples
Gray (Default)
Black
Primary
Error
Modifiable
Text color inherits from the parent by default. No default color is applied, allowing for full customization.
Here's an example with custom colors specified:
Size
Underline
With Icon
Disabled
asChild
Composition
API Reference
LinkButton.Root
This component is based on the <button> element and supports all of its props. And adds:
| Prop | Type | Default |
|---|---|---|
variant | "gray" | "black" | "primary" | "error" | "modifiable" | "gray" |
size | "medium" | "small" | "medium" |
underline | boolean | false |
asChild | boolean |
LinkButton.Icon
The LinkButton.Icon component is polymorphic, allowing you to change the underlying HTML element using the as prop.
| Prop | Type | Default |
|---|---|---|
as | React.ElementType | div |