shadcn.io is not affiliated with official shadcn/ui
The button is just its label until the pointer arrives or it takes keyboard focus; then a line grows under the text, from the middle or from either side, and shrinks back when it leaves. On the current page's link it stays drawn.
Use it for navigation, footer links, page menus, or a secondary action next to a filled one. It starts as a ghost button with the hover background turned off, and it wraps the Button, so variant and size work as usual. The line is a CSS transition on its scale, with no animation library and no JavaScript; with reduced motion it's drawn full width and fades in instead of growing.
Installation
Usage
import { UnderlineButton } from "@/components/ui/underline-button";<UnderlineButton>Read the case study</UnderlineButton>Demos
Origin
origin sets where the line grows from: center, the default, start or end. Start and end follow the text direction, so in a right-to-left layout the start is on the right.
Thickness and Color
thickness is the line's height in pixels, 2 by default, and underlineColor its color, any CSS color, the primary color by default. Here a hairline in the foreground color suits a footer link, and a thick primary line with a slower duration suits a large call to action.
Current Page
The line stays drawn on a button with aria-current="page" (or "true"), so a menu marks where people are with no extra prop. Screen readers announce it as the current page too.
Accessibility
The underline is decoration and hidden from assistive tech; the label is the button's name. It grows on keyboard focus as well as hover, and the button keeps its usual focus ring. aria-current both draws the line and tells screen readers which item is current.
- Enter
- Activate the button.
- Space
- Activate the button.
FAQ
How do I make the underline thicker?
Set thickness in pixels; 2 is the default. Use 1 for a hairline or 3 to 4 for a bolder line. The Thickness and Color demo shows 1 and 4.
How do I change the underline's color?
Pass underlineColor, any CSS color, such as var(--foreground). The default is the primary color, and the label keeps the button's text color.
Can the line grow from one side?
Yes. origin="start" grows it from where the text starts and origin="end" from where it ends, as in the Origin demo.
How do I keep the underline on the current page's link?
Pass aria-current="page". The line stays drawn while it's set, as in the Current Page demo.
API Reference
UnderlineButton
data-slot="underline-button-line"
children?ReactNode
The label; the line is as wide as it is.
duration?number
How long the underline takes to grow, in milliseconds.
- Default
300
focusableWhenDisabled?boolean
Whether the button should be focusable when disabled.
- Default
false
nativeButton?boolean
Whether the component renders a native <button> element when replacing it
via the render prop.
Set to false if the rendered element is not a button (for example, <div>).
- Default
true
origin?"center" | "start" | "end"
Where the underline grows from: the middle of the label, or the side the text starts or ends on (the start is the right in a right-to-left layout).
- Type
"center" | "start" | "end"- Default
"center"
thickness?number
How thick the underline is, in pixels.
- Default
2
underlineColor?string
The underline's color, any CSS color.
- Default
"var(--primary)"
render?ReactElement | ((props: object, state: object) => ReactElement)
Replace the host element or compose it with another component.
- Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string
Merged with the part's classes.
--underline-button-colorstring
The line's color.
--underline-button-durationstring
How long the line takes to grow (300ms).
--underline-button-thicknessstring
The line's thickness (2px).
Related
Arrow Button
Hover revealsHover hint for links that go on
- Pro
Liquid Button
Hover revealsA bolder hover reveal
- Pro
Stitch Button
SurfacesAnother drawn-line look
- Pro
Shine Button
SurfacesSubtle sweep across the label
Kbd Button
ControlsText action with its shortcut
- Pro
Pill Button
SurfacesWhen it needs a filled pair