# 링크형 버튼

버튼처럼 보이지만 요소는 링크예요. 어딘가로 가는 자리에 써요.

- Type: component
- Theme: base
- Tags: arc, component, button, link, navigation, cta
- English title: Button link
- URL: https://dsui.vercel.app/items/button-link

## Used items

- [버튼](https://dsui.vercel.app/items/button): 눌렀을 때 살짝 눌리고 소리가 나는 기본 버튼이에요.

## Used by

- [404 페이지](https://dsui.vercel.app/items/not-found): 페이지를 찾지 못했을 때 보여 주는 블록이에요. 4, 0, 4를 도형으로 쓴 일러스트와 돌아갈 길을 함께 안내해요.

## Controls

| Key | Type | Label | Default |
| --- | --- | --- | --- |
| variant | select | 종류 | primary |
| size | select | 크기 | md |
| disabled | boolean | 비활성 | false |

## Source

### button-link.module.css

```css
/* Everything the look needs comes from components/arc/button; this file only adds what an anchor needs and a button does not. */
.link { text-decoration: none; transition: color var(--duration-fast) var(--ease-standard), background var(--duration-fast) var(--ease-standard), border-color var(--duration-fast) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard), transform var(--duration-fast) var(--ease-standard); }
/* The Button presses on a Motion spring, which needs a real button element, so the link does the same ~.97 in CSS. */
.link:active:not([aria-disabled="true"]) { transform: scale(.97); }
.label { display: inline-flex; align-items: center; gap: var(--space-2); white-space: nowrap; }
.label svg { flex: none; }
/* An anchor is never `:disabled`, so the Button's disabled rules cannot reach it: the same look is repeated here. */
.link[aria-disabled="true"] { color: color-mix(in oklab, var(--foreground) 55%, transparent); background: var(--border); border-color: transparent; box-shadow: none; cursor: not-allowed; pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .link { transition: none; } .link:active { transform: none; } }

```

### button-link.tsx

```tsx
"use client";

import type { AnchorHTMLAttributes, ReactNode } from "react";
import buttonStyles from "../button/button.module.css";
import { ArcLink } from "../lib/arc-provider";
import { playUiSound } from "../sound/play";
import styles from "./button-link.module.css";

export type ButtonLinkVariant = "primary" | "secondary" | "ghost" | "accent" | "danger";
export type ButtonLinkSize = "sm" | "md" | "lg";

export interface ButtonLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href"> {
  href: string;
  variant?: ButtonLinkVariant;
  size?: ButtonLinkSize;
  /** Greys the link out and stops it being followed or focused. */
  disabled?: boolean;
  /** Skip the click sound, for a link that already makes its own sound. */
  silent?: boolean;
  children?: ReactNode;
}

/**
 * A link that looks like the Button. Going somewhere is a navigation, not an action, so this stays an anchor: it opens
 * in a new tab with the usual modifiers, copies as a URL and is announced as a link. It borrows the Button's own
 * stylesheet so the two never drift apart, and adds the anchor-only parts — a CSS press (the Button's Motion press
 * needs a real `<button>`) and a disabled state built on `aria-disabled`, which an anchor has instead of `:disabled`.
 */
export function ButtonLink({
  className,
  href,
  variant = "primary",
  size = "md",
  disabled = false,
  silent = false,
  onClick,
  children,
  ...props
}: ButtonLinkProps) {
  const classes = [buttonStyles.button, buttonStyles[variant], buttonStyles[size], styles.link, className].filter(Boolean).join(" ");
  return (
    <ArcLink
      {...props}
      href={href}
      className={classes}
      aria-disabled={disabled || undefined}
      // A disabled link keeps its place in the page but leaves the tab order, the way a disabled button does.
      tabIndex={disabled ? -1 : props.tabIndex}
      onClick={event => {
        if (disabled) { event.preventDefault(); return; }
        if (!silent) playUiSound("click");
        onClick?.(event);
      }}
    >
      <span className={styles.label}>{children}</span>
    </ArcLink>
  );
}

ButtonLink.displayName = "ButtonLink";

export default ButtonLink;

```
