Skip to content
Noksha UIv0.1
Colour theme

actions

Button

Nine visual weights across six tones, six hover effects on a separate axis, and the grouped, toggle, copy and back-to-top patterns built on them.

Examples

Bold styles

Twenty-eight one-off looks built with plain `className` on top of the same Button — neon, glass, neumorphic, a rotating gradient border, a real ripple, a hold-to-confirm fill, a save button that morphs into a checkmark, a confetti burst, and a real progress fill, among others.

Neon glow
Shimmer sweep
Glass
Neumorphic
Gradient ring
Color swap
Ripple
Magnetic arrow
Underline draw
Spin on hover
Glossy
Split
Notification
Gradient text
Hold to confirm
Morph to check
Equalizer
Sticker
Brutalist
Bounce on hover
Rotating border
Sparkle badge
Loading shell
Copy link
Confetti
Progress
3D flip
Floating action

Variants

Visual weight, from most to least emphasis. Colour never comes from here.

Expressive variants

Four surfaces beyond the flat set. Every colour in them is the tone you passed — the gradient runs along the tone’s own OKLCH ramp, and the glow is that same tone as a shadow.

Hover effects

Point at each one. Effect is a third axis, independent of variant and tone, so any of the six works on any of the nine variants. All of them are inert under prefers-reduced-motion.

Three axes at once

Variant, tone and effect chosen separately. None of these is a preset — there is no variant named after a use case anywhere in the library.

Grouped, toggled, copied

Three shipped patterns built on Button rather than beside it — ButtonGroup joins buttons into one control, ToggleButton holds a pressed state on aria-pressed, and CopyButton writes to the clipboard and announces it.

Floating buttons

FloatingButton pins one action to a corner — icon-only, extended with its label, in the gradient/glow/soft variants, with a pulse effect, or anchored to a different corner. FloatingMenu is the expandable version, shown here with hidden labels and a custom open icon too — both stack their actions with a staggered entrance, close on Escape or an outside press, and hand focus back to the trigger.

Simple

Extended

Gradient variant

Glow + pulse effect

Top-left placement

Bottom-center placement

Expandable — open it

Icons only — labels hidden

Custom open icon

Back to top

Scroll the panel to watch it work. A passive rAF-throttled listener, visibility rather than opacity so it leaves the tab order while hidden, and focus handed back to the top after the scroll. Pass a container ref and it watches that element instead of the window.

1. Scroll this panel

It watches this panel rather than the window, because it was handed a ref to it. Past 160px it fades in; click it and the panel scrolls smoothly back.

2. Keep going

It watches this panel rather than the window, because it was handed a ref to it. Past 160px it fades in; click it and the panel scrolls smoothly back.

3. Almost there

It watches this panel rather than the window, because it was handed a ref to it. Past 160px it fades in; click it and the panel scrolls smoothly back.

4. Past the threshold

It watches this panel rather than the window, because it was handed a ref to it. Past 160px it fades in; click it and the panel scrolls smoothly back.

5. The button is up now

It watches this panel rather than the window, because it was handed a ref to it. Past 160px it fades in; click it and the panel scrolls smoothly back.

6. Still scrolling

It watches this panel rather than the window, because it was handed a ref to it. Past 160px it fades in; click it and the panel scrolls smoothly back.

7. Nearly the end

It watches this panel rather than the window, because it was handed a ref to it. Past 160px it fades in; click it and the panel scrolls smoothly back.

8. The end

It watches this panel rather than the window, because it was handed a ref to it. Past 160px it fades in; click it and the panel scrolls smoothly back.

The button is a sibling of the scrolling panel, not a child of it — an absolutely positioned child would scroll away with the content. Give it no container and it watches the window instead, which is the usual full-page back-to-top.

Tones

One prop repaints the whole button. Every tone declares the same seven slots, so there is no variant × tone matrix to maintain.

Sizes

Heights and padding derive from --noksha-density; one variable retunes them all.

Shapes

A third box axis beside size — `round` is the pill, `circle` the icon disc. It composes with every variant and every size rather than being a look of its own. Two rows show what that composes into: a notification bell and cart with count badges, an unread dot, a wishlist heart, an avatar; then back, close, a real play/pause toggle, a star rating, a kebab menu, a mic-mute toggle, help and location — all `Button`/`ToggleButton`, no bespoke controls.

circle
round
real-world32
more
sizes

With icons

Loading

Click Save — the button holds its exact width while the spinner is in. `loadingPlacement="icon"` swaps the indicator into the leading icon’s slot instead, which keeps the label readable, and `loadingIcon` replaces the spinner with any node.

Own the source

Noksha ships as a package and as copy-paste source. Take the files and they are yours to change. These are read from the generated registry, so they are exactly what the library ships — never a paraphrase of it.

Take these files, and spinner, internal helpers along with them — the imports below point at them. Or let the CLI do it: npx @noksha-ui/cli add button writes them, follows the same dependency graph, and fixes up the imports.

import { cx } from '@noksha-ui/core';
import * as React from 'react';
import type { ButtonGroupProps } from './button.types.js';

const ORIENTATION = {
  horizontal: [
    'flex-row',
    '[&>*:not(:first-child)]:-ml-px',
    '[&>*:not(:first-child)]:rounded-s-none',
    '[&>*:not(:last-child)]:rounded-e-none',
  ].join(' '),
  vertical: [
    'flex-col',
    '[&>*:not(:first-child)]:-mt-px',
    '[&>*:not(:first-child)]:rounded-t-none',
    '[&>*:not(:last-child)]:rounded-b-none',
  ].join(' '),
};

export const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(function ButtonGroup(
  { orientation = 'horizontal', attached = true, role = 'group', className, children, ...rest },
  ref,
) {
  return (
    <div
      ref={ref}
      role={role}
      className={cx(
        'inline-flex',
        attached
          ? cx(ORIENTATION[orientation], '[&>*]:relative [&>*:focus-visible]:z-10 [&>*:hover]:z-10')
          : cx(orientation === 'vertical' ? 'flex-col' : 'flex-row', 'gap-2'),
        className,
      )}
      {...rest}
    >
      {children}
    </div>
  );
});
ButtonGroup.displayName = 'ButtonGroup';

API reference

Accepted values

ButtonVariant
solidsoftoutlineghostlinkgradientglassglowdashed
ButtonEffect
noneliftsheenwipepulsetilt
ButtonSize
xssmmdlgxl
ButtonShape
defaultroundcircle
ButtonLoadingPlacement
overlayicon
FloatingPlacement
bottom-rightbottom-leftbottom-centertop-righttop-lefttop-center

ButtonBaseProps

Also accepts everything from Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'>.

PropTypeDescription
variantButtonVariantVisual weight.
toneButtonToneSemantic colour.
effectButtonEffectHover motion. Orthogonal to `variant` and `tone`.
sizeButtonSize
shapeButtonShapeCorner treatment. `round` is a pill, `circle` is the icon-only disc.
fullWidthbooleanStretches to the container width.
loadingbooleanShows a spinner and blocks interaction, holding the button's width.
loadingLabelstringReplaces the spinner's default label for screen readers.
loadingIconReact.ReactNodeReplaces the default `Spinner` while loading.
loadingPlacementButtonLoadingPlacement`overlay` (default) blanks the button and centres the indicator, holding the width. `icon` swaps it into the leading icon's slot and leaves the label readable.
iconReact.ReactNode
trailingIconReact.ReactNode
asChildbooleanRenders the child element instead of a `<button>`, merging props onto it.

ButtonGroupProps

Also accepts everything from React.HTMLAttributes<HTMLDivElement>.

PropTypeDescription
orientation'horizontal' | 'vertical'Lay the buttons out in a column instead of a row.
attachedbooleanJoin the buttons into one control, collapsing the borders between them.

CopyButtonProps

Also accepts everything from Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children' | 'value'>.

PropTypeDescription
value*stringThe text written to the clipboard.
labelstringAccessible name, and the label when `withLabel` is set.
copiedLabelstringReplaces `label` while the confirmation is showing.
timeoutnumberHow long the confirmation stays up, in milliseconds.
withLabelbooleanShow the label beside the icon instead of only as an accessible name.
variantButtonVariant
toneButtonTone
sizeButtonSize
onCopied(value: string) => voidCalled after a successful copy, never after a rejected one.

FloatingAnchorProps

PropTypeDescription
placementFloatingPlacement
offsetnumberDistance from the two edges it is pinned to, in pixels.
containerReact.RefObject<HTMLElement | null>Pins inside this element instead of the viewport. It must be positioned.

ScrollToTopProps

Also accepts everything from Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'>, FloatingAnchorProps.

PropTypeDescription
showAfternumberPixels scrolled before the button appears.
labelstring
focusTargetstringSelector for the element to focus after scrolling. Defaults to the body.
variantButtonVariant
sizeButtonSize
iconReact.ReactNode

FloatingButtonProps

Also accepts everything from Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'>, FloatingAnchorProps.

PropTypeDescription
label*stringAccessible name, and the visible text when `extended` is set.
icon*React.ReactNode
extendedbooleanShow the label beside the icon instead of only as an accessible name.
variantButtonVariant
toneButtonTone
sizeButtonSize
effectButtonEffect

FloatingAction

PropTypeDescription
id*string
label*string
icon*React.ReactNode
toneButtonTone
onSelect() => void

FloatingMenuProps

Also accepts everything from Omit<React.HTMLAttributes<HTMLDivElement>, 'children' | 'onSelect'>, FloatingAnchorProps.

PropTypeDescription
label*stringAccessible name for the trigger and for the action stack it opens.
actions*FloatingAction[]
iconReact.ReactNode
openIconReact.ReactNodeReplaces the icon while the stack is open.
hideActionLabelsbooleanHide the label chip beside each action.
openbooleanPresent means controlled.
defaultOpenboolean
onOpenChange(open: boolean) => void
variantButtonVariant
toneButtonTone
sizeButtonSize