Button

Displays a button or a component that looks like a button.

import { Button } from "~/components/button";

export default function ButtonVariantsDemo() {
  return (
    <div class="flex flex-wrap gap-4">
      <Button>Default</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="destructive">Destructive</Button>
      <Button variant="outline">Outline</Button>
      <Button variant="ghost">Ghost</Button>
      <Button variant="link">Link</Button>
    </div>
  );
}

Installation

CLI

Run the following command to add the component to your project:

npx @ark-preset/cli@latest add button

Manual

Create the recipe file at src/components/recipes/button.ts:

import { tv, type VariantProps } from "tailwind-variants";

export const buttonVariants = tv({
  base: "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition duration-300 motion-safe:hover:-translate-y-0.5 motion-safe:active:translate-y-0.5 active:duration-150 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&>svg]:pointer-events-none [&>svg]:size-4 [&>svg]:shrink-0 [[data-icon]]:pointer-events-none [[data-icon=inline-start]]:-me-0.5 [[data-icon=inline-end]]:-ms-0.5",
  variants: {
    variant: {
      default: "bg-primary text-primary-foreground hover:bg-primary/90",
      destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
      outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
      secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
      ghost: "hover:bg-accent hover:text-accent-foreground",
      link: "text-primary underline-offset-4 hover:underline",
    },
    size: {
      xs: "h-7 px-2 text-xs",
      sm: "h-8 px-2.5 text-sm",
      md: "h-9 px-3 py-1.5",
      lg: "h-10 px-6",
      icon: "size-8",
      "icon-xs": "size-7 [&>svg]:size-4",
      "icon-sm": "size-8 [&>svg]:size-4",
      "icon-md": "size-9 [&>svg]:size-5",
      "icon-lg": "size-10 [&>svg]:size-6",
    },
  },
  defaultVariants: {
    variant: "default",
    size: "sm",
  },
});

export type ButtonVariants = VariantProps<typeof buttonVariants>;

Create the component file at src/components/button/index.tsx:

import { splitProps, type Component, children } from "solid-js";
import { ButtonVariants, buttonVariants } from "../recipes/button";
import { ark, HTMLArkProps } from "@ark-ui/solid/factory";

type ButtonProps = HTMLArkProps<"button"> & ButtonVariants;

const Button: Component<ButtonProps> = (props) => {
  const [local, others] = splitProps(props, ["class", "variant", "size", "children"]);

  const resolvedChildren = children(() => local.children);
  return (
    <ark.button
      class={buttonVariants({
        variant: local.variant,
        size: local.size,
        class: local.class,
      })}
      {...others}
    >
      {resolvedChildren()}
    </ark.button>
  );
};

export { Button, buttonVariants };

Note: Make sure your project has the Tailwind CSS theme variables set up (--background, --foreground, --ring, --border, etc.) or override the utility classes to match your design system.

Usage

Basic Usage

import { Button } from "~/components/button";

export default function ButtonBasicDemo() {
  return <Button>Click me</Button>;
}

Variants

Use the variant prop to change the visual style.

import { Button } from "~/components/button";

export default function ButtonVariantsDemo() {
  return (
    <div class="flex flex-wrap gap-4">
      <Button>Default</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="destructive">Destructive</Button>
      <Button variant="outline">Outline</Button>
      <Button variant="ghost">Ghost</Button>
      <Button variant="link">Link</Button>
    </div>
  );
}

Sizes

Use the size prop to change the button size.

import { Button } from "~/components/button";

export default function ButtonSizesDemo() {
  return (
    <div class="flex flex-wrap items-center gap-4">
      <Button size="xs">Xs</Button>
      <Button size="sm">Sm</Button>
      <Button size="md">Md</Button>
      <Button size="lg">Lg</Button>
      <Button size="icon-xs">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="16"
          height="16"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <path d="M5 12h14" />
          <path d="M12 5v14" />
        </svg>
      </Button>
      <Button size="icon-sm">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="16"
          height="16"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <path d="M5 12h14" />
          <path d="M12 5v14" />
        </svg>
      </Button>
      <Button size="icon-md">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="16"
          height="16"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <path d="M5 12h14" />
          <path d="M12 5v14" />
        </svg>
      </Button>
      <Button size="icon-lg">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="16"
          height="16"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <path d="M5 12h14" />
          <path d="M12 5v14" />
        </svg>
      </Button>
    </div>
  );
}

Disabled

Add the disabled prop to disable interaction.

import { Button } from "~/components/button";

export default function ButtonDisabledDemo() {
  return (
    <div class="flex flex-wrap gap-4">
      <Button disabled>Default</Button>
      <Button variant="secondary" disabled>
        Secondary
      </Button>
      <Button variant="destructive" disabled>
        Destructive
      </Button>
      <Button variant="outline" disabled>
        Outline
      </Button>
      <Button variant="ghost" disabled>
        Ghost
      </Button>
      <Button variant="link" disabled>
        Link
      </Button>
    </div>
  );
}

With Icon

import { Button } from "~/components/button";

export default function ButtonIconsDemo() {
  return (
    <div class="flex flex-wrap items-center gap-4">
      <Button variant="outline" size="icon">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="16"
          height="16"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <path d="M5 12h14" />
          <path d="M12 5v14" />
        </svg>
      </Button>
      <Button variant="outline">
        <svg
          data-icon="inline-start"
          xmlns="http://www.w3.org/2000/svg"
          width="16"
          height="16"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <path d="M5 12h14" />
          <path d="M12 5v14" />
        </svg>
        New Branch
      </Button>
      <Button variant="outline">
        Fork
        <svg
          data-icon="inline-end"
          xmlns="http://www.w3.org/2000/svg"
          width="16"
          height="16"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <path d="M5 12h14" />
          <path d="M12 5v14" />
        </svg>
      </Button>
    </div>
  );
}

Spinner

import { Button, Spinner } from "~/components/button";

export default function ButtonSpinnerDemo() {
  return (
    <div class="flex flex-wrap items-center gap-4">
      <Button disabled variant="outline" size="icon">
        <Spinner />
      </Button>
      <Button disabled variant="outline">
        <Spinner data-icon="inline-start" />
        Loading...
      </Button>
      <Button disabled variant="outline">
        Saving
        <Spinner data-icon="inline-end" />
      </Button>
    </div>
  );
}

Use the buttonVariants helper to render a link that looks like a button.

import { buttonVariants } from "~/components/button";

export default function ButtonLinkDemo() {
  return (
    <div class="flex flex-wrap gap-4">
      <a class={buttonVariants({ variant: "outline" })} href="#">
        Click here
      </a>
    </div>
  );
}

API Reference

PropTypeDefault
variant"default" | "secondary" | "destructive" | "outline" | "ghost" | "link""default"
size"xs" | "sm" | "md" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-md" | "icon-lg""sm"
disabledbooleanfalse
classstring