# Underline Hover (Moonarc)

A link underline that enters from the left on hover and leaves to the right, so the exit continues the entrance instead of rewinding it. One background gradient, no pseudo-element, wraps across lines. Pure CSS; triggered by the link around it.

- Import: `import UnderlineHover from '@moonarc/core/UnderlineHover'`
- Install: `npx astro add moonarc` · copy-paste: `npx shadcn@latest add https://moonarc.dev/r/underline-hover.json`
- Tier A · category text · trigger hover
- Readout: `<UnderlineHover thickness="0.08em">`
- Browser support: widely (every browser)
- Measured cost: 0 B JS (CSS 4.9 kB raw)
- Page: https://moonarc.dev/components/underline-hover/

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `as` | `HTMLTag` | `'span'` | Element to render. Use a or wrap it in one. |
| `thickness` | `string` | `'0.08em'` | Line thickness. |
| `offset` | `string` | `'0.12em'` | Gap between the text and the line. |
| `color` | `string` | `'currentColor'` | Line colour. |

## Usage

```astro
<a href="/docs/"><UnderlineHover>Read the docs</UnderlineHover></a>
<UnderlineHover as="a" href="/pro/" thickness="2px" color="var(--ma-ink)">Pro</UnderlineHover>
```

## Reduced motion

The line appears and disappears without sliding.

## With ClientRouter

CSS-only; nothing to rebind.

## Craft

- Enter left, leave right: background-position flips instantly between the two states and only background-size transitions, so the line grows from the left and, on leave, shrinks toward the right: one direction of travel, like a pen that keeps going.
- Entrance on the preset spring at the full duration, exit at the fast duration on ease-out: the acknowledgement lingers, the release gets out of the way.
- A gradient on the element itself, not a pseudo-element, so box-decoration-break: clone paints the line on every wrapped line of a multi-line link.
- Hover is read from the containing link or button, so the whole target counts; focus-visible draws the line for keyboard users.

## Replaces

- underline hover effects (Animata, uiverse)
- Framer Motion layout underlines

## Source

```astro
---
/**
 * UnderlineHover — the line enters from the left and leaves to the right.
 * One background gradient: its size grows on hover while anchored left,
 * and shrinks on leave while anchored right, so the exit runs the same
 * direction as the entrance. Works on the element or from the link or
 * button around it. Zero JS.
 */
import type { HTMLAttributes, HTMLTag } from 'astro/types';

interface Props extends HTMLAttributes<'span'> {
  /** Element to render. */
  as?: HTMLTag;
  /** Line thickness, any CSS length. */
  thickness?: string;
  /** Gap between the text and the line. */
  offset?: string;
  /** Line colour. */
  color?: string;
}

const { as: Tag = 'span', thickness = '0.08em', offset = '0.12em', color = 'currentColor', class: className, style, ...rest } = Astro.props;
const vars = [`--ma-underline-w:${thickness}`, `--ma-underline-offset:${offset}`, `--ma-underline-color:${color}`, typeof style === 'string' ? style : ''].filter(Boolean).join(';');
---

<Tag class:list={['ma-underline', className]} style={vars} {...rest}><slot /></Tag>

<style is:global>
  @layer components {
    :where(.ma-underline) {
      text-decoration: none;
      padding-bottom: var(--ma-underline-offset, 0.12em);
      background-image: linear-gradient(var(--ma-underline-color, currentColor), var(--ma-underline-color, currentColor));
      background-repeat: no-repeat;
      background-size: 0% var(--ma-underline-w, 0.08em);
      background-position: 100% 100%;
      -webkit-box-decoration-break: clone;
      box-decoration-break: clone;
      transition: background-size var(--ma-duration-fast) var(--ma-ease-out);
    }
    @media (hover: hover) {
      :where(.ma-underline:hover),
      :where(:is(a, button):hover .ma-underline) {
        background-size: 100% var(--ma-underline-w, 0.08em);
        background-position: 0% 100%;
        transition-duration: var(--ma-duration);
        transition-timing-function: var(--ma-ease);
      }
    }
    :where(.ma-underline:focus-visible),
    :where(:is(a, button):focus-visible .ma-underline) {
      background-size: 100% var(--ma-underline-w, 0.08em);
      background-position: 0% 100%;
    }
    @media (prefers-reduced-motion: reduce) {
      :where(.ma-underline) {
        transition: none;
      }
    }
  }
</style>

```
