AniUI

Button

A pressable button with variants, sizes, icons, and loading states.

import { Button } from "@/components/ui/button";

export function MyScreen() {
  return (
    <Button onPress={() => console.log("pressed")}>
      Click me
    </Button>
  );
}

Installation

npx @aniui/cli add button

Usage

app/index.tsx
import { Button } from "@/components/ui/button";

export function MyScreen() {
  return (
    <Button onPress={() => console.log("pressed")}>
      Click me
    </Button>
  );
}

Variants

Six visual styles for different contexts and emphasis levels.

<Button variant="default">Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="link">Link</Button>

Sizes

<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>

With Icons

Pass any React Native element as icon (leading) or iconAfter (trailing). Use size="icon" for icon-only buttons.

import { Ionicons } from "@expo/vector-icons";
// Icon before text
<Button icon={<Ionicons name="add" size={18} color="#fff" />}>
  Create
</Button>
// Icon after text
<Button iconAfter={<Ionicons name="send" size={18} color="#fff" />}>
  Send
</Button>
// Icon only
<Button size="icon" icon={<Ionicons name="heart" size={18} color="#fff" />} />

Loading

Set loading to show a spinner and disable interaction. The spinner color adapts to the variant.

<Button loading>Saving...</Button>
<Button loading variant="outline">Loading</Button>
<Button loading variant="destructive">Deleting</Button>

Disabled

<Button disabled>Disabled</Button>
<Button disabled variant="outline">Disabled</Button>

Full Width

Add className="w-full" for block-level buttons.

<Button className="w-full">Full Width Button</Button>
<Button className="w-full" variant="outline">Full Width Outline</Button>

Props

PropTypeDefault
variant
"default" | "secondary" | "outline" | "ghost" | "destructive" | "link"
"default"
size
"sm" | "md" | "lg" | "icon"
"md"
icon
React.ReactNode
iconAfter
React.ReactNode
loading
boolean
false
disabled
boolean
false
className
string
textClassName
string
children
string

Also accepts all Pressable props from React Native.

Source

components/ui/button.tsx
import React from "react";
import { Pressable, Text, ActivityIndicator } from "react-native";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const buttonVariants = cva(
  "flex-row items-center justify-center rounded-md min-h-12 min-w-12",
  {
    variants: {
      variant: {
        default: "bg-primary",
        secondary: "bg-secondary",
        outline: "border border-input bg-transparent",
        ghost: "bg-transparent",
        destructive: "bg-destructive",
        link: "bg-transparent",
      },
      size: {
        sm: "px-3 py-1.5 gap-1.5",
        md: "px-4 py-2.5 gap-2",
        lg: "px-6 py-3.5 gap-2.5",
        icon: "h-10 w-10 p-0",
      },
    },
    defaultVariants: { variant: "default", size: "md" },
  }
);
const buttonTextVariants = cva("text-center font-medium", {
  variants: {
    variant: {
      default: "text-primary-foreground",
      secondary: "text-secondary-foreground",
      outline: "text-foreground",
      ghost: "text-foreground",
      destructive: "text-destructive-foreground",
      link: "text-primary underline",
    },
    size: { sm: "text-sm", md: "text-base", lg: "text-lg", icon: "text-sm" },
  },
  defaultVariants: { variant: "default", size: "md" },
});
export interface ButtonProps
  extends React.ComponentPropsWithoutRef<typeof Pressable>,
    VariantProps<typeof buttonVariants> {
  className?: string;
  textClassName?: string;
  children?: string;
  icon?: React.ReactNode;
  iconAfter?: React.ReactNode;
  loading?: boolean;
}
export function Button({ variant, size, className, textClassName, children, icon, iconAfter, loading, disabled, ...props }: ButtonProps) {
  const isDisabled = disabled || loading;
  const light = variant === "default" || variant === "destructive";
  return (
    <Pressable
      className={cn(buttonVariants({ variant, size }), isDisabled && "opacity-50", className)}
      accessibilityRole="button"
      accessible={true}
      disabled={isDisabled}
      {...props}
    >
      {loading ? (
        <ActivityIndicator size="small" color={light ? "hsl(0,0%,98%)" : "hsl(240,5.9%,10%)"} />
      ) : icon ?? null}
      {children ? (
        <Text className={cn(buttonTextVariants({ variant, size }), textClassName)}>{children}</Text>
      ) : null}
      {!loading && iconAfter ? iconAfter : null}
    </Pressable>
  );
}