Kosmesis
Components

Button

A button, wrapping Morphos's polymorphic Button primitive with shadcn-style variants.

Installation

npx kosmesis add button
pnpm dlx kosmesis add button
yarn dlx kosmesis add button
bunx kosmesis add button

Install the following dependencies:

npm install @morphos/inputs class-variance-authority
pnpm add @morphos/inputs class-variance-authority
yarn add @morphos/inputs class-variance-authority
bun add @morphos/inputs class-variance-authority

Copy and paste the following code into your project.

button.tsx
import { cva, type VariantProps } from "class-variance-authority";import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import { Button as MorphosButton, type ButtonProps as MorphosButtonProps  } from "@morphos/inputs";import { cn } from "@/lib/utils";export const buttonVariants = cva(  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:border-ring [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",  {    variants: {      variant: {        default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",        destructive:          "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20",        outline:          "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",        secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",        ghost: "hover:bg-accent hover:text-accent-foreground",        link: "text-primary underline-offset-4 hover:underline",      },      size: {        default: "h-9 px-4 py-2",        sm: "h-8 rounded-md px-3 text-xs",        lg: "h-10 rounded-md px-6",        icon: "size-9",      },    },    defaultVariants: {      variant: "default",      size: "default",    },  },);export interface ButtonProps extends MorphosButtonProps, VariantProps<typeof buttonVariants> {}@Component()export class Button extends StatelessComponent<ButtonProps> {  render() {    const { variant, size, class: cls, ...rest } = this.props;    return <MorphosButton class={cn(buttonVariants({ variant, size }), cls)} {...rest} />;  }}

Install the following dependencies:

npm install @morphos/inputs @praxisjs/css
pnpm add @morphos/inputs @praxisjs/css
yarn add @morphos/inputs @praxisjs/css
bun add @morphos/inputs @praxisjs/css

Copy and paste the following code into your project.

button.tsx
import { StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import { Button as MorphosButton, type ButtonProps as MorphosButtonProps  } from "@morphos/inputs";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);/** * `$variant*`/`$size*` fields are looked up by name in `render()` rather than composed through a * `cva`-style helper — `@praxisjs/css` has no variant-authority equivalent, so a plain * `Record<string, string>` lookup plays that role for every Kosmesis component in this style * system. Exported so components that render a native button-like element out of the Button * primitive (e.g. `AlertDialogAction`/`AlertDialogCancel`) can reuse the exact same scoped * classes via their own `@Styled(ButtonStyles)` field. */export class ButtonStyles extends Stylesheet {  $root = this.css({    display: "inline-flex",    alignItems: "center",    justifyContent: "center",    gap: "0.5rem",    whiteSpace: "nowrap",    borderRadius: `calc(${t.radius} - 2px)`,    fontSize: "0.875rem",    fontWeight: 500,    outline: "none",    cursor: "pointer",    transition: "color 120ms ease, background-color 120ms ease, border-color 120ms ease, box-shadow 120ms ease",  })    .on("&[data-disabled], &:disabled", { pointerEvents: "none", opacity: 0.5 })    .focusVisible({      borderColor: t.ring,      boxShadow: `0 0 0 3px color-mix(in oklab, ${t.ring} 50%, transparent)`,    })    .on("& svg", { pointerEvents: "none", flexShrink: 0, width: "1rem", height: "1rem" });  $variantDefault = this.css({ backgroundColor: t.primary, color: t.primaryForeground }).hover({    backgroundColor: `color-mix(in oklab, ${t.primary} 90%, transparent)`,  });  $variantDestructive = this.css({ backgroundColor: t.destructive, color: "white" })    .hover({ backgroundColor: `color-mix(in oklab, ${t.destructive} 90%, transparent)` })    .focusVisible({ boxShadow: `0 0 0 3px color-mix(in oklab, ${t.destructive} 20%, transparent)` });  $variantOutline = this.css({    border: `1px solid ${t.input}`,    backgroundColor: t.background,  }).hover({ backgroundColor: t.accent, color: t.accentForeground });  $variantSecondary = this.css({ backgroundColor: t.secondary, color: t.secondaryForeground }).hover({    backgroundColor: `color-mix(in oklab, ${t.secondary} 80%, transparent)`,  });  $variantGhost = this.css({ backgroundColor: "transparent" }).hover({    backgroundColor: t.accent,    color: t.accentForeground,  });  $variantLink = this.css({ color: t.primary, textUnderlineOffset: "4px" }).hover({ textDecoration: "underline" });  $sizeDefault = this.css({ height: "2.25rem", padding: "0 1rem" });  $sizeSm = this.css({ height: "2rem", padding: "0 0.75rem", fontSize: "0.75rem" });  $sizeLg = this.css({ height: "2.5rem", padding: "0 1.5rem" });  $sizeIcon = this.css({ height: "2.25rem", width: "2.25rem", padding: "0" });}export type ButtonVariant = "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";export type ButtonSize = "default" | "sm" | "lg" | "icon";export interface ButtonProps extends MorphosButtonProps {  variant?: ButtonVariant;  size?: ButtonSize;}@Component()export class Button extends StatelessComponent<ButtonProps> {  @Styled(ButtonStyles) $s!: ButtonStyles;  render() {    const { variant = "default", size = "default", class: cls, ...rest } = this.props;    const variants: Record<ButtonVariant, string> = {      default: this.$s.$variantDefault,      destructive: this.$s.$variantDestructive,      outline: this.$s.$variantOutline,      secondary: this.$s.$variantSecondary,      ghost: this.$s.$variantGhost,      link: this.$s.$variantLink,    };    const sizes: Record<ButtonSize, string> = {      default: this.$s.$sizeDefault,      sm: this.$s.$sizeSm,      lg: this.$s.$sizeLg,      icon: this.$s.$sizeIcon,    };    return <MorphosButton class={cx(this.$s.$root, variants[variant], sizes[size], cls)} {...rest} />;  }}

Examples

About

Wraps @morphos/inputs's Button — a polymorphic primitive supporting an as prop for link-buttons. Kosmesis adds variant/size props backed by class-variance-authority, matching shadcn/ui's own API exactly.

Usage

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

@Component()
class MyPage extends StatefulComponent {
  render() {
    return (
      <div>
        <Button>Default</Button>
        <Button variant="destructive">Destructive</Button>
        <Button variant="outline">Outline</Button>
        <Button variant="secondary">Secondary</Button>
        <Button variant="ghost">Ghost</Button>
        <Button variant="link">Link</Button>
        <Button size="sm">Small</Button>
        <Button size="lg">Large</Button>
        <Button as="a" href="/dashboard">Go to dashboard</Button>
      </div>
    );
  }
}

Props

PropTypeDefault
variant"default" | "destructive" | "outline" | "secondary" | "ghost" | "link""default"
size"default" | "sm" | "lg" | "icon""default"
as"button" | "a" | "span" | "div""button"
disabledboolean
onClick(event: MouseEvent) => void

All other props (href, type, aria-*, class, id, children, ...) pass through to Morphos's Button unchanged — see its docs for the full list.

disabled only becomes the native disabled attribute when as="button"; otherwise it's exposed as aria-disabled="true", since non-button elements don't support disabled natively.

On this page