Navigation

LinkIcon

Icon-only link with a real 24px touch target

Preview

size
icon

Code

import css from "./LinkIcon.module.css";

type Size = "sm" | "md" | "lg";

interface ResponsiveSize {
  base: Size;
  mobile?: Size;
  tablet?: Size;
  desktop?: Size;
  wide?: Size;
}

interface LinkIconProps {
  size: Size | ResponsiveSize;
  icon: string;
  href: string;
  target?: string;
  "aria-label": string;
}

function getSizeClasses(size: Size | ResponsiveSize): string {
  if (typeof size === "string") {
    return css[size];
  }

  const classes = [css[`${size.base}_base`]];

  if (size.mobile) classes.push(css[`${size.mobile}_mobile`]);
  if (size.tablet) classes.push(css[`${size.tablet}_tablet`]);
  if (size.desktop) classes.push(css[`${size.desktop}_desktop`]);
  if (size.wide) classes.push(css[`${size.wide}_wide`]);

  return classes.join(" ");
}

export default function LinkIcon({
  size,
  icon,
  href,
  target,
  "aria-label": ariaLabel,
}: LinkIconProps) {
  return (
    <a
      className={`
        ${css.link}
        ${getSizeClasses(size)}
      `}
      href={href}
      target={target}
      rel={target === "_blank" ? "noopener noreferrer" : undefined}
      aria-label={ariaLabel}
    >
      <img src={icon} alt="" className={css.link_icon} />
    </a>
  );
}

Props

PropTypeRequiredDescription
size"sm" | "md" | "lg" | ResponsiveSizerequiredSize of the box and the icon; the box never drops under 24. Fixed or responsive.
iconstringrequiredPath of the icon image.
hrefstringrequiredLink destination.
targetstring—Where it opens. With "_blank" it adds rel="noopener noreferrer".
aria-labelstringrequiredAccessible name of the link (icon-only: without it, a screen reader would announce nothing).