ToggleButton

free

ToggleButton composes Button while swapping active and inactive content from one boolean state.

Preview
Dark
import { useState } from 'react';
import ToggleButton from '@/components/composites/ToggleButton';

export default function UsageDemo() {
  const [visible, setVisible] = useState(false);

  return (
    <ToggleButton
      active={visible}
      inactiveContent={{ children: 'Show details' }}
      activeContent={{ children: 'Hide details' }}
      type="button"
      aria-pressed={visible}
      onClick={() => setVisible((value) => !value)}
    />
  );
}

Installation

Add this component with the NateUI CLI.

npx nateui@latest add ToggleButton

Examples

Icon Content

Preview
Dark
import { useState } from 'react';
import { PiSidebar, PiSidebarSimple } from 'react-icons/pi'
import ToggleButton from '@/components/composites/ToggleButton';

export default function IconContentDemo() {
  const [open, setOpen] = useState(false);

  return (
    <ToggleButton
      active={open}
      inactiveContent={{ icon: <PiSidebarSimple /> }}
      activeContent={{ icon: <PiSidebar /> }}
      type="button"
      variant="ghost"
      shape="circle"
      aria-label={open ? 'Close panel' : 'Open panel'}
      aria-pressed={open}
      onClick={() => setOpen((value) => !value)}
    />
  );
}

Mixed Content

Preview
Dark
import { useState } from 'react';
import { PiEye, PiEyeSlash } from 'react-icons/pi'
import ToggleButton from '@/components/composites/ToggleButton';

export default function MixedContentDemo() {
  const [previewing, setPreviewing] = useState(false);

  return (
    <ToggleButton
      active={previewing}
      inactiveContent={{
        icon: <PiEye />,
        children: 'Show preview',
      }}
      activeContent={{
        icon: <PiEyeSlash />,
        children: 'Hide preview',
      }}
      type="button"
      variant="subtle"
      aria-pressed={previewing}
      onClick={() => setPreviewing((value) => !value)}
    />
  );
}

Disabled

Preview
Dark
import { useState } from 'react';
import ToggleButton from '@/components/composites/ToggleButton';
import { PiChecks, PiCopy } from 'react-icons/pi'

export default function DisabledDemo() {
  const [copy, setCopy] = useState(false);
  return (
    <ToggleButton
      disabled
      active={copy}
      inactiveContent={{ 
        icon: <PiCopy />,
        children: 'Copy',
      }}
      activeContent={{ 
        icon: <PiChecks/>,
        children: 'Copied',
      }}
      type="button"
      variant="subtle"
      aria-label={copy ? 'Copied' : 'Copy'}
      aria-pressed={copy}
      onClick={() => setCopy((value) => !value)}
    />
  );
}

Disabled Active Style

Preview
Dark
import { useState } from 'react';
import ToggleButton from '@/components/composites/ToggleButton';
import { PiHeart } from 'react-icons/pi'

export default function DisabledDemo() {
  const [like, setLike] = useState(true);
  return (
    <ToggleButton
      disabledActiveStyle
      active={like}
      inactiveContent={{ 
        icon: <PiHeart />,
        children: 'Like',
      }}
      activeContent={{ 
        icon: <PiHeart className="fill-current" />,
        children: 'Liked',
      }}
      type="button"
      variant="ghost"
      aria-label={like ? 'Liked' : 'Like'}
      aria-pressed={like}
      onClick={() => setLike((value) => !value)}
    />
  );
}

API

ToggleButton accepts the content slots below and forwards the remaining Button props and native button props to Button.

PropDescriptionTypeDefault
activeSelects activeContent and applies Button's active visual state when true.booleanfalse
disabledActiveStyleKeeps the active content while preventing active from being passed to Button.booleanfalse
activeContentContent rendered while the toggle is active.ToggleButtonContent
inactiveContentContent rendered while the toggle is inactive.ToggleButtonContent
asElementRoot element or component rendered by Button.ElementType'button'
blockExpands the control to the full width of its container.booleanfalse
classNameAdditional classes or a state-aware class callback.ButtonClassName
clickFeedbackEnables Button's press-down feedback.booleantrue
destructiveApplies Button's destructive intent palette.booleanfalse
disabledPrevents interaction and applies the disabled state.booleanfalse
iconAlignmentPlaces an icon before or after the active/inactive label.ButtonIconAlignment'start'
loadingShows Button's loading spinner and prevents repeat activation.booleanfalse
onClickClick handler forwarded to Button.ButtonClickHandler
refRef forwarded to Button's underlying element.Ref<HTMLButtonElement>
shapeBorder radius treatment.ButtonShape'round'
sizeButton control size.ControlSizeConfigProvider default
variantButton visual style.ButtonVariant'default'
...nativePropsNative props forwarded to the rendered element, such as type, aria-pressed, and aria-label.NativeButtonProps<E>

Types

type ToggleButtonContent = {
  children?: ReactNode;
  icon?: string | ReactNode;
};
 
type ButtonIconAlignment = 'start' | 'end';
type ButtonShape = 'round' | 'circle' | 'none';
type ControlSize = 'sm' | 'md' | 'lg';
type ButtonVariant = 'solid' | 'subtle' | 'default' | 'ghost' | 'link';

ToggleButton does not infer an accessibility relationship from active; pass aria-pressed, aria-expanded, and related native props according to what the control operates.