Skip to content
0

Button ​

useButton(options) creates a styled <button>; buttonClass(options) returns the class list for SSR or framework templates.

Variants ​

ts
import { useButton } from "@katanakit/ui";

row.append(
  useButton({ label: "Primary" }),
  useButton({ label: "Secondary", variant: "secondary" }),
  useButton({ label: "Ghost", variant: "ghost" }),
  useButton({ label: "Danger", variant: "danger" }),
);

Sizes and states ​

ts
useButton({ label: "Small", size: "sm" });
useButton({ label: "Large", size: "lg" });
useButton({ label: "Loading", loading: true });
useButton({ label: "Disabled", disabled: true });

Contract

Every component in the kit follows one shape. Pure builders return a class string and touch nothing; factories build a DOM element and need a browser.

SurfaceImportNeeds a DOM
Pure class builderbuttonClassNo — safe during SSR
Element factoryuseButtonYes — throws with a pointer to the pure surface
// Server and templates — pure
<div class={ buttonClass({ /* … */ }) }></div>

// Browser — imperative
document.body.append(useButton({ /* … */ }));

Class names are unprefixed. The root class is btn, not kk-btn, and every component rule is wrapped in @layer katana.

That matters because these names collide 1:1 with daisyUI. Unlayered CSS always beats layered CSS regardless of specificity or import order, so loading both is deterministic instead of "whoever came last". See why.

Options ​

OptionTypeDefaultNotes
labelstring""Visible text.
variant"primary" | "secondary" | "ghost" | "danger""primary"Visual style.
size"sm" | "md" | "lg""md"Padding and font size.
type"button" | "submit" | "reset""button"Native type.
loadingbooleanfalseSpinner, aria-busy and disabled interaction.
disabledbooleanfalseNative disabled state.
onClick(event: MouseEvent) => void—Attached with addEventListener.

SSR

The factories need a DOM. On the server use buttonClass() and render the markup yourself — the classes are stable strings.

Released under the MIT License.