"use client"
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import {
Button as AriaButton,
composeRenderProps,
type ButtonProps as AriaButtonProps,
} from "react-aria-components"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
[
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors",
/* Disabled */
"data-disabled:pointer-events-none data-disabled:opacity-50 ",
/* Focus Visible */
"data-focus-visible:outline-hidden data-focus-visible:ring-1 data-focus-visible:ring-ring ",
/* Resets */
"focus-visible:outline-hidden",
],
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow-sm data-hovered:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-xs data-hovered:bg-destructive/90",
outline:
"border border-input bg-background shadow-xs data-hovered:bg-accent data-hovered:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-xs data-hovered:bg-secondary/80",
ghost: "data-hovered:bg-accent data-hovered:text-accent-foreground",
link: "text-primary underline-offset-4 data-hovered:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
interface ButtonProps
extends AriaButtonProps, VariantProps<typeof buttonVariants> {}
const Button = ({ className, variant, size, ...props }: ButtonProps) => {
return (
<AriaButton
className={composeRenderProps(className, (className) =>
cn(
buttonVariants({
variant,
size,
className,
})
)
)}
{...props}
/>
)
}
export { Button, buttonVariants }
export type { ButtonProps }
Button Outline
Try the interaction, copy the React source files, or download the complete example.
About Button Outline
Button Outline is an animated React and Tailwind CSS button by James Shopland. Try the interaction, copy the React source, or download the example with creator credit.
- Effects and categories
- Hover effects, Animated
- Using this example
- Copy a TSX file or download the original source and standalone demo. Test keyboard focus, motion and contrast in your own project.
Usage, Tailwind and licensing questions
Read the source files
registry/new-york/example/button/button-outline.tsx
import { Button } from "@/registry/new-york/ui/button"
export default function ButtonOutline() {
return <Button variant="outline">Outline</Button>
}
registry/new-york/ui/button.tsx
"use client"
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import {
Button as AriaButton,
composeRenderProps,
type ButtonProps as AriaButtonProps,
} from "react-aria-components"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
[
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors",
/* Disabled */
"data-disabled:pointer-events-none data-disabled:opacity-50 ",
/* Focus Visible */
"data-focus-visible:outline-hidden data-focus-visible:ring-1 data-focus-visible:ring-ring ",
/* Resets */
"focus-visible:outline-hidden",
],
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow-sm data-hovered:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-xs data-hovered:bg-destructive/90",
outline:
"border border-input bg-background shadow-xs data-hovered:bg-accent data-hovered:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-xs data-hovered:bg-secondary/80",
ghost: "data-hovered:bg-accent data-hovered:text-accent-foreground",
link: "text-primary underline-offset-4 data-hovered:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
interface ButtonProps
extends AriaButtonProps, VariantProps<typeof buttonVariants> {}
const Button = ({ className, variant, size, ...props }: ButtonProps) => {
return (
<AriaButton
className={composeRenderProps(className, (className) =>
cn(
buttonVariants({
variant,
size,
className,
})
)
)}
{...props}
/>
)
}
export { Button, buttonVariants }
export type { ButtonProps }
lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
import { siteConfig } from "@/config/site"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function absoluteUrl(path: string) {
return `${siteConfig.url}${path}`
}
config/site.ts
export const siteConfig = {
name: "JollyUI",
url: "https://jollyui.dev",
ogImage: "https://jollyui.dev/og.jpg",
description:
"shadcn/ui compatible react aria components that you can copy and paste into your apps. Accessible. Customizable. Open Source. Formerly known as shadcn-aria.",
links: {
twitter: "https://twitter.com/jollyshopland",
github: "https://github.com/jolbol1/jolly-ui",
shadcnReactAria: "https://ui.shadcn.com/docs/changelog/2026-07-react-aria",
},
}
export type SiteConfig = typeof siteConfig
demo.tsx
export { default as default } from "@/registry/new-york/example/button/button-outline";CSS
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
@layer properties;
@layer theme, base, components, utilities;
@layer theme {
:root, :host {
--font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-mono: "JetBrains Mono Variable", ui-monospace, SFMono-Regular, Menlo, Monaco,
Consolas, "Liberation Mono", "Courier New", monospace;
--spacing: 0.25rem;
--text-xs: 0.75rem;
--text-xs--line-height: calc(1 / 0.75);
--text-sm: 0.875rem;
--text-sm--line-height: calc(1.25 / 0.875);
--font-weight-medium: 500;
--radius-md: calc(var(--radius) - 2px);
--default-transition-duration: 150ms;
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
--default-font-family: var(--font-sans);
--default-mono-font-family: var(--font-mono);
--color-background: hsl(var(--background));
--color-primary: hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));
--color-secondary: hsl(var(--secondary));
--color-secondary-foreground: hsl(var(--secondary-foreground));
--color-accent: hsl(var(--accent));
--color-accent-foreground: hsl(var(--accent-foreground));
--color-input: hsl(var(--input));
--color-ring: hsl(var(--ring));
--color-destructive: hsl(var(--destructive));
--color-destructive-foreground: hsl(var(--destructive-foreground));
}
}
@layer base {
*, ::after, ::before, ::backdrop, ::file-selector-button {
box-sizing: border-box;
margin: 0;
padding: 0;
border: 0 solid;
}
html, :host {
line-height: 1.5;
-webkit-text-size-adjust: 100%;
tab-size: 4;
font-family: var(--default-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
font-feature-settings: var(--default-font-feature-settings, normal);
font-variation-settings: var(--default-font-variation-settings, normal);
-webkit-tap-highlight-color: transparent;
}
hr {
height: 0;
color: inherit;
border-top-width: 1px;
}
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
h1, h2, h3, h4, h5, h6 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
-webkit-text-decoration: inherit;
text-decoration: inherit;
}
b, strong {
font-weight: bolder;
}
code, kbd, samp, pre {
font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
font-feature-settings: var(--default-mono-font-feature-settings, normal);
font-variation-settings: var(--default-mono-font-variation-settings, normal);
font-size: 1em;
}
small {
font-size: 80%;
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
table {
text-indent: 0;
border-color: inherit;
border-collapse: collapse;
}
:-moz-focusring:where(:not(iframe)) {
outline: auto;
}
progress {
vertical-align: baseline;
}
summary {
display: list-item;
}
ol, ul, menu {
list-style: none;
}
img, svg, video, canvas, audio, iframe, embed, object {
display: block;
vertical-align: middle;
}
img, video {
max-width: 100%;
height: auto;
}
button, input, select, optgroup, textarea, ::file-selector-button {
font: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
color: inherit;
border-radius: 0;
background-color: transparent;
opacity: 1;
}
:where(select:is([multiple], [size])) optgroup {
font-weight: bolder;
}
:where(select:is([multiple], [size])) optgroup option {
padding-inline-start: 20px;
}
::file-selector-button {
margin-inline-end: 4px;
}
::placeholder {
opacity: 1;
}
@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {
::placeholder {
color: currentcolor;
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
}
textarea {
resize: vertical;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-date-and-time-value {
min-height: 1lh;
text-align: inherit;
}
::-webkit-datetime-edit {
display: inline-flex;
}
::-webkit-datetime-edit-fields-wrapper {
padding: 0;
}
::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {
padding-block: 0;
}
::-webkit-calendar-picker-indicator {
line-height: 1;
}
:-moz-ui-invalid {
box-shadow: none;
}
button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button {
appearance: button;
}
::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
height: auto;
}
[hidden]:where(:not([hidden="until-found"])) {
display: none !important;
}
}
@layer utilities {
.inline-flex {
display: inline-flex;
}
.size-9 {
width: calc(var(--spacing) * 9);
height: calc(var(--spacing) * 9);
}
.h-8 {
height: calc(var(--spacing) * 8);
}
.h-9 {
height: calc(var(--spacing) * 9);
}
.h-10 {
height: calc(var(--spacing) * 10);
}
.items-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.rounded-md {
border-radius: var(--radius-md);
}
.border {
border-style: var(--tw-border-style);
border-width: 1px;
}
.border-input {
border-color: var(--color-input);
}
.bg-background {
background-color: var(--color-background);
}
.bg-destructive {
background-color: var(--color-destructive);
}
.bg-primary {
background-color: var(--color-primary);
}
.bg-secondary {
background-color: var(--color-secondary);
}
.px-3 {
padding-inline: calc(var(--spacing) * 3);
}
.px-4 {
padding-inline: calc(var(--spacing) * 4);
}
.px-8 {
padding-inline: calc(var(--spacing) * 8);
}
.py-2 {
padding-block: calc(var(--spacing) * 2);
}
.text-sm {
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
}
.text-xs {
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
}
.font-medium {
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
}
.whitespace-nowrap {
white-space: nowrap;
}
.text-destructive-foreground {
color: var(--color-destructive-foreground);
}
.text-primary {
color: var(--color-primary);
}
.text-primary-foreground {
color: var(--color-primary-foreground);
}
.text-secondary-foreground {
color: var(--color-secondary-foreground);
}
.underline-offset-4 {
text-underline-offset: 4px;
}
.shadow-sm {
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.shadow-xs {
--tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.outline {
outline-style: var(--tw-outline-style);
outline-width: 1px;
}
.transition-colors {
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.focus-visible\:outline-hidden:focus-visible {
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
}
.data-disabled\:pointer-events-none[data-disabled] {
pointer-events: none;
}
.data-disabled\:opacity-50[data-disabled] {
opacity: 50%;
}
.data-focus-visible\:ring-1[data-focus-visible] {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.data-focus-visible\:ring-ring[data-focus-visible] {
--tw-ring-color: var(--color-ring);
}
.data-focus-visible\:outline-hidden[data-focus-visible] {
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
}
.data-hovered\:bg-accent[data-hovered] {
background-color: var(--color-accent);
}
.data-hovered\:bg-destructive\/90[data-hovered] {
background-color: color-mix(in srgb, hsl(var(--destructive)) 90%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-destructive) 90%, transparent);
}
}
.data-hovered\:bg-primary\/90[data-hovered] {
background-color: color-mix(in srgb, hsl(var(--primary)) 90%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-primary) 90%, transparent);
}
}
.data-hovered\:bg-secondary\/80[data-hovered] {
background-color: color-mix(in srgb, hsl(var(--secondary)) 80%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-secondary) 80%, transparent);
}
}
.data-hovered\:text-accent-foreground[data-hovered] {
color: var(--color-accent-foreground);
}
.data-hovered\:underline[data-hovered] {
text-decoration-line: underline;
}
}
:root {
--background: #fff;
--foreground: #111;
--primary: #171717;
--primary-foreground: #fff;
--secondary: #eee;
--secondary-foreground: #171717;
--muted: #eee;
--muted-foreground: #666;
--accent: #eee;
--accent-foreground: #171717;
--border: #ddd;
--input: #ddd;
--ring: #777;
--destructive: #dc2626;
--destructive-foreground: #fff;
--popover: #fff;
--popover-foreground: #111;
--success: #15803d;
--success-foreground: #fff;
--radius: .625rem;
--hairline: 1px;
}
.dark {
--background: #090909;
--foreground: #fafafa;
--primary: #fafafa;
--primary-foreground: #171717;
--secondary: #262626;
--secondary-foreground: #fafafa;
--muted: #262626;
--muted-foreground: #a3a3a3;
--accent: #262626;
--accent-foreground: #fafafa;
--border: #333;
--input: #333;
--popover: #171717;
--popover-foreground: #fafafa;
}
@property --tw-border-style {
syntax: "*";
inherits: false;
initial-value: solid;
}
@property --tw-font-weight {
syntax: "*";
inherits: false;
}
@property --tw-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-inset-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-inset-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-inset-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-ring-color {
syntax: "*";
inherits: false;
}
@property --tw-ring-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-inset-ring-color {
syntax: "*";
inherits: false;
}
@property --tw-inset-ring-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-ring-inset {
syntax: "*";
inherits: false;
}
@property --tw-ring-offset-width {
syntax: "<length>";
inherits: false;
initial-value: 0px;
}
@property --tw-ring-offset-color {
syntax: "*";
inherits: false;
initial-value: #fff;
}
@property --tw-ring-offset-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-outline-style {
syntax: "*";
inherits: false;
initial-value: solid;
}
@layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::before, ::after, ::backdrop {
--tw-border-style: solid;
--tw-font-weight: initial;
--tw-shadow: 0 0 #0000;
--tw-shadow-color: initial;
--tw-shadow-alpha: 100%;
--tw-inset-shadow: 0 0 #0000;
--tw-inset-shadow-color: initial;
--tw-inset-shadow-alpha: 100%;
--tw-ring-color: initial;
--tw-ring-shadow: 0 0 #0000;
--tw-inset-ring-color: initial;
--tw-inset-ring-shadow: 0 0 #0000;
--tw-ring-inset: initial;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-offset-shadow: 0 0 #0000;
--tw-outline-style: solid;
}
}
}