Installation
npx @park-ui/cli add buttonAdd Component
Copy the code snippet below into you components folder.
'use client'
import { ark } from '@ark-ui/react/factory'
import { createContext, mergeProps } from '@ark-ui/react/utils'
import { type ComponentProps, forwardRef, useMemo } from 'react'
import { styled } from 'styled-system/jsx'
import { type ButtonVariantProps, button } from 'styled-system/recipes'
import { Group, type GroupProps } from './group'
import { Loader } from './loader'
interface ButtonLoadingProps {
/**
* If `true`, the button will show a loading spinner.
* @default false
*/
loading?: boolean | undefined
/**
* The text to show while loading.
*/
loadingText?: React.ReactNode | undefined
/**
* The spinner to show while loading.
*/
spinner?: React.ReactNode | undefined
/**
* The placement of the spinner
* @default "start"
*/
spinnerPlacement?: 'start' | 'end' | undefined
}
type BaseButtonProps = ComponentProps<typeof BaseButton>
const BaseButton = styled(ark.button, button)
export interface ButtonProps extends BaseButtonProps, ButtonLoadingProps {}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(props, ref) {
const propsContext = useButtonPropsContext()
const buttonProps = useMemo(
() => mergeProps<ButtonProps>(propsContext, props),
[propsContext, props],
)
const { loading, loadingText, children, spinner, spinnerPlacement, ...rest } = buttonProps
return (
<BaseButton
type="button"
ref={ref}
{...rest}
data-loading={loading ? '' : undefined}
disabled={loading || rest.disabled}
>
{!props.asChild && loading ? (
<Loader spinner={spinner} text={loadingText} spinnerPlacement={spinnerPlacement}>
{children}
</Loader>
) : (
children
)}
</BaseButton>
)
})
export interface ButtonGroupProps extends GroupProps, ButtonVariantProps {}
export const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>(
function ButtonGroup(props, ref) {
const [variantProps, otherProps] = useMemo(() => button.splitVariantProps(props), [props])
return (
<ButtonPropsProvider value={variantProps}>
<Group ref={ref} {...otherProps} />
</ButtonPropsProvider>
)
},
)
const [ButtonPropsProvider, useButtonPropsContext] = createContext<ButtonVariantProps>({
name: 'ButtonPropsContext',
hookName: 'useButtonPropsContext',
providerName: '<PropsProvider />',
strict: false,
})
Integrate Recipe
Integrate this recipe in to your Panda config.
import { defineRecipe } from '@pandacss/dev'
export const button = defineRecipe({
className: 'button',
jsx: ['Button', 'IconButton', 'CloseButton', 'ButtonGroup'],
base: {
alignItems: 'center',
appearance: 'none',
borderRadius: 'l2',
cursor: 'pointer',
display: 'inline-flex',
flexShrink: '0',
fontWeight: 'semibold',
gap: '2',
isolation: 'isolate',
justifyContent: 'center',
outline: '0',
position: 'relative',
transition: 'colors',
transitionProperty: 'background-color, border-color, color, box-shadow',
userSelect: 'none',
verticalAlign: 'middle',
whiteSpace: 'nowrap',
_icon: {
flexShrink: '0',
},
_disabled: {
layerStyle: 'disabled',
},
focusVisibleRing: 'outside',
},
defaultVariants: {
variant: 'solid',
size: 'md',
},
variants: {
variant: {
solid: {
bg: 'colorPalette.solid.bg',
color: 'colorPalette.solid.fg',
_hover: {
bg: 'colorPalette.solid.bg.hover',
},
},
surface: {
bg: 'colorPalette.surface.bg',
borderWidth: '1px',
borderColor: 'colorPalette.surface.border',
color: 'colorPalette.surface.fg',
_hover: {
borderColor: 'colorPalette.surface.border.hover',
},
_active: {
bg: 'colorPalette.surface.bg.active',
},
_on: {
bg: 'colorPalette.surface.bg.active',
},
},
subtle: {
bg: 'colorPalette.subtle.bg',
color: 'colorPalette.subtle.fg',
_hover: {
bg: 'colorPalette.subtle.bg.hover',
},
_active: {
bg: 'colorPalette.subtle.bg.active',
},
_on: {
bg: 'colorPalette.subtle.bg.active',
},
},
outline: {
borderWidth: '1px',
borderColor: 'colorPalette.outline.border',
color: 'colorPalette.outline.fg',
_hover: {
bg: 'colorPalette.outline.bg.hover',
},
_active: {
bg: 'colorPalette.outline.bg.active',
},
_on: {
bg: 'colorPalette.outline.bg.active',
},
},
plain: {
color: 'colorPalette.plain.fg',
_hover: {
bg: 'colorPalette.plain.bg.hover',
},
_active: {
bg: 'colorPalette.plain.bg.active',
},
_on: {
bg: 'colorPalette.plain.bg.active',
},
},
},
size: {
'2xs': { h: '6', minW: '6', textStyle: 'xs', px: '2', _icon: { boxSize: '3.5' } },
xs: { h: '8', minW: '8', textStyle: 'sm', px: '2.5', _icon: { boxSize: '4' } },
sm: { h: '9', minW: '9', textStyle: 'sm', px: '3', _icon: { boxSize: '4' } },
md: { h: '10', minW: '10', textStyle: 'sm', px: '3.5', _icon: { boxSize: '5' } },
lg: { h: '11', minW: '11', textStyle: 'md', px: '4', _icon: { boxSize: '5' } },
xl: { h: '12', minW: '12', textStyle: 'md', px: '4.5', _icon: { boxSize: '5.5' } },
'2xl': { h: '16', minW: '16', textStyle: 'xl', px: '6', _icon: { boxSize: '6' } },
},
},
})
Usage
import { Button } from '@/components/ui'
<Button>Click me</Button>
Examples
Sizes
Use the size prop to change the size of the button.
Variants
Use the variant prop to change the appearance of the button.
Icon
Use icons within a button
Colors
Use the colorPalette prop to change the appearance of the button.
Disabled
Use the disabled prop to disable the button.
Loading
Pass the loading and loadingText props to the button to show a loading spinner and add a loading text.
Button Group
Use the ButtonGroup component to group buttons together. This component allows you pass common recipe properties to inner buttons.
To create an attached button group, pass the attached prop to the ButtonGroup component.
As Link
Use the asChild prop to render a button as a link.
Ref
Here's how to access the underlying element reference
'use client'
import { useRef } from 'react'
import { Button } from '@/components/ui'
export const App = () => {
const ref = useRef<HTMLButtonElement>(null)
return <Button ref={ref}>Click me</Button>
}
Props
| Prop | Default | Type |
|---|---|---|
variant | 'solid' | 'solid' | 'surface' | 'subtle' | 'outline' | 'plain' |
size | 'md' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' |