Button

A Button component that handles variant stylings and states

  • 6 variant styles for each use case.
  • 3 sizes sm, md, lg following same spacing design-system.
  • Builtin left/right icon placement.
  • Builtin spinner and internal loading state.
  • Builtin optional tooltip.

Import

import { Button } from "h2o-library";

Usage

Variants

<Button label="Primary" />
<Button variant="secondary" label="Secondary" />
<Button variant="ghost" label="Ghost" />
<Button variant="danger" label="Danger" />
<Button variant="warning" label="Warning" />
<Button variant="success" label="Success" />

Sizes

Icon Placement

Icons can go on the right (default) or left via iconPosition.

<Button icon="download" label="Download" />
<Button icon="plus" label="Add item" iconPosition="left" />

Icon Only

Always pair with tooltip — it will be used as the accessible aria-label automatically.

<Button variant="primary" icon="plus" iconOnly tooltip="Add new item" />;

Loading State

isLoading disables the button and replaces content with a spinner. Setting aria-busy is handled automatically.

<Button label="Saving…" isLoading />;

Full Width

<Button label="Submit form" fullWidth />;

Disabled

Playground

Loading playground…

Usage in Forms

Uncontrolled submit

<form
  onSubmit={(e) => {
    e.preventDefault(); /* handle */
  }}
>
  <Input name="email" placeholder="Email" />
  <Button type="submit" label="Subscribe" />
</form>;

Controlled with useState

const [loading, setLoading] = useState(false);

async function handleSubmit() {
  setLoading(true);
  await submitData();
  setLoading(false);
}

<Button
  label={loading ? "Saving…" : "Save"}
  isLoading={loading}
  onClick={handleSubmit}
/>;

With react-hook-form

import { useForm } from "react-hook-form";

function LoginForm() {
  const {
    handleSubmit,
    formState: { isSubmitting },
  } = useForm();

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* fields */}
      <Button
        type="submit"
        label={isSubmitting ? "Signing in…" : "Sign in"}
        isLoading={isSubmitting}
        fullWidth
      />
    </form>
  );
}

Keyboard

Native <button> behavior:

KeyAction
Enter / SpaceActivates the button (does not submit a form unless type="submit").
Tab / Shift+TabMove focus.

Props

PropTypeDefaultDescription
labelstringButton text. Can be replaced by children.
variant"primary" | "secondary" | "ghost" | "danger" | "warning" | "success""primary"Visual style of the button.
size"sm" | "md" | "lg""md"Height and font size of the button.
iconIconTypeundefinedIcon name from the icon set.
iconPosition"left" | "right""right"Side of the label where the icon is placed.
iconOnlybooleanfalseRenders only the icon. Pair with tooltip for accessibility.
iconStrokeWidthnumber1.5Stroke width passed to the Icon component.
isLoadingbooleanfalseShows a spinner, sets aria-busy, and disables the button.
fullWidthbooleanfalseStretches the button to fill its container.
disabledbooleanfalseDisables the button.
tooltipstringundefinedTooltip shown on hover. Also used as aria-label for iconOnly.
tooltipPosition"top" | "bottom" | "left" | "right""top"Position of the tooltip.
classNamestring""Additional CSS classes.

Extends all native <button> HTML attributes.

Design Guidelines

  • Use primary for the single main action on a page or in a form.
  • Use secondary for alternative or cancel actions.
  • Use ghost for low-emphasis actions in toolbars or dense UI.
  • Use danger for destructive actions (delete, remove, revoke).
  • Always provide tooltip when using iconOnly — it doubles as the accessible label.

Accessibility

  • Buttons are keyboard accessible and triggered with Enter or Space.
  • iconOnly buttons derive aria-label from tooltip automatically.
  • isLoading sets aria-busy="true" and disables the button to prevent double-submission.