"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 ring-offset-background transition-colors",
/* Disabled */
"data-disabled:pointer-events-none data-disabled:opacity-50 ",
/* Focus Visible */
"data-focus-visible:outline-hidden data-focus-visible:ring-2 data-focus-visible:ring-ring data-focus-visible:ring-offset-2",
/* Resets */
"focus-visible:outline-hidden",
],
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground data-hovered:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground data-hovered:bg-destructive/90",
outline:
"border border-input bg-background data-hovered:bg-accent data-hovered:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground 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-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "size-10",
},
},
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 Secondary
Try the interaction, copy the React source files, or download the complete example.
About Button Secondary
Button Secondary 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/default/example/button/button-secondary.tsx
import { Button } from "@/registry/default/ui/button"
export default function ButtonSecondary() {
return <Button variant="secondary">Secondary</Button>
}
registry/default/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 ring-offset-background transition-colors",
/* Disabled */
"data-disabled:pointer-events-none data-disabled:opacity-50 ",
/* Focus Visible */
"data-focus-visible:outline-hidden data-focus-visible:ring-2 data-focus-visible:ring-ring data-focus-visible:ring-offset-2",
/* Resets */
"focus-visible:outline-hidden",
],
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground data-hovered:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground data-hovered:bg-destructive/90",
outline:
"border border-input bg-background data-hovered:bg-accent data-hovered:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground 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-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "size-10",
},
},
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/default/example/button/button-secondary";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-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-10 {
width: calc(var(--spacing) * 10);
height: calc(var(--spacing) * 10);
}
.h-9 {
height: calc(var(--spacing) * 9);
}
.h-10 {
height: calc(var(--spacing) * 10);
}
.h-11 {
height: calc(var(--spacing) * 11);
}
.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));
}
.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;
}
.ring-offset-background {
--tw-ring-offset-color: var(--color-background);
}
.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-2[data-focus-visible] {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + 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\:ring-offset-2[data-focus-visible] {
--tw-ring-offset-width: 2px;
--tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
}
.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-outline-style {
syntax: "*";
inherits: false;
initial-value: solid;
}
@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;
}
@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-outline-style: solid;
--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;
}
}
}