V1.0

Base Components

Link Button

Buttons that seamlessly integrate with text content.

Installation

Install the following dependencies:

terminal
npm install @radix-ui/react-slot

Create a link-button.tsx file and paste the following code into it.

/components/ui/link-button.tsx
// AlignUI LinkButton v0.0.0
 
import * 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:pointer-events-none disabled:text-text-disabled-300 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: 'h-5 gap-1 text-label-sm',
        icon: 'size-5',
      },
      small: {
        root: 'h-4 gap-1 text-label-xs',
        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 };

Update the import paths to match your project setup.

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:

PropTypeDefault
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.

PropTypeDefault
as
React.ElementType
div
© 2024 AlignUI Design System. All rights reserved.