Skip to content
Spira
Recipes

Blocks

Composed, copy-paste examples built from Spira primitives.

Chat

Live messaging UI — avatar header, bubbles and composer.

SP
Spira Teamonline
Beta
Hey! Did you try the new Spira design system?
Yeah — the rounded look is fire 🔥
Right? And it's fully themeable.
"use client";

import { useState, type FormEvent } from "react";
import { Send } from "lucide-react";
import { Avatar, AvatarFallback, AvatarImage, Badge, Button, Input, cn } from "@/components/ui";

type Message = { id: number; from: "me" | "them"; text: string };

export function Chat() {
  const [messages, setMessages] = useState<Message[]>([
    { id: 1, from: "them", text: "Hey! Did you try the new Spira design system?" },
    { id: 2, from: "me", text: "Yeah — the rounded look is fire 🔥" },
  ]);
  const [draft, setDraft] = useState("");

  function send(event: FormEvent) {
    event.preventDefault();
    const text = draft.trim();
    if (!text) return;
    setMessages((prev) => [...prev, { id: Date.now(), from: "me", text }]);
    setDraft("");
  }

  return (
    <div className="flex h-[460px] w-full max-w-md flex-col overflow-hidden rounded-card border border-border bg-surface-raised">
      <header className="flex items-center gap-3 border-b border-border p-4">
        <Avatar>
          <AvatarImage src="https://github.com/oyaksaile.png" alt="Spira Team" />
          <AvatarFallback>SP</AvatarFallback>
        </Avatar>
        <div className="flex flex-col">
          <span className="text-sm font-medium">Spira Team</span>
          <span className="text-xs text-muted">online</span>
        </div>
        <Badge variant="soft" className="ml-auto">Beta</Badge>
      </header>

      <div className="flex flex-1 flex-col gap-3 overflow-y-auto p-4">
        {messages.map((message) => (
          <div key={message.id} className={cn("flex", message.from === "me" ? "justify-end" : "justify-start")}>
            <div className={cn("max-w-[75%] rounded-lg px-4 py-2 text-sm", message.from === "me" ? "bg-[image:var(--gradient-accent)] text-accent-foreground" : "bg-surface text-foreground")}>
              {message.text}
            </div>
          </div>
        ))}
      </div>

      <form onSubmit={send} className="flex items-center gap-2 border-t border-border p-3">
        <Input value={draft} onChange={(e) => setDraft(e.target.value)} placeholder="Message…" aria-label="Message" />
        <Button type="submit" size="icon" aria-label="Send"><Send className="size-4" /></Button>
      </form>
    </div>
  );
}

Sign in

Authentication card with fields and remember-me.

Welcome back

Sign in to your Spira account.

import { Button, Checkbox, Input, Label } from "@/components/ui";

export function SignIn() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-5 rounded-card border border-border bg-surface-raised p-6">
      <div className="flex flex-col gap-1">
        <h3 className="font-display text-display-sm tracking-tight">Welcome back</h3>
        <p className="text-sm text-muted">Sign in to your Spira account.</p>
      </div>
      <div className="flex flex-col gap-2">
        <Label htmlFor="email">Email</Label>
        <Input id="email" type="email" placeholder="[email protected]" />
      </div>
      <div className="flex flex-col gap-2">
        <Label htmlFor="password">Password</Label>
        <Input id="password" type="password" placeholder="••••••••" />
      </div>
      <label className="flex items-center gap-2 text-sm">
        <Checkbox defaultChecked />
        Remember me
      </label>
      <Button className="w-full">Sign in</Button>
    </div>
  );
}

Pricing

Plan card with a feature list.

Pro
$19/month

Everything you need to ship.

  • Unlimited projects
  • Custom themes
  • Priority support
  • Usage analytics
import { Check } from "lucide-react";
import { Badge, Button } from "@/components/ui";

const features = ["Unlimited projects", "Custom themes", "Priority support", "Usage analytics"];

export function Pricing() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-5 rounded-card border border-border bg-surface-raised p-6">
      <div className="flex flex-col gap-3">
        <Badge variant="accent" className="w-fit">Pro</Badge>
        <div className="flex items-end gap-1">
          <span className="font-display text-display-lg tracking-tight">$19</span>
          <span className="pb-2 text-sm text-muted">/month</span>
        </div>
        <p className="text-sm text-muted">Everything you need to ship.</p>
      </div>
      <ul className="flex flex-col gap-2.5 text-sm">
        {features.map((feature) => (
          <li key={feature} className="flex items-center gap-2">
            <Check className="size-4 shrink-0 text-accent" />
            {feature}
          </li>
        ))}
      </ul>
      <Button className="w-full">Choose Pro</Button>
    </div>
  );
}

Product

E-commerce card — image, rating, price and add-to-cart.

Aurora Hoodie−20%
YKYaksa Store4.9

Aurora Hoodie

Heavyweight organic cotton, garment-dyed.

$72$90
In stock
"use client";

import { useState } from "react";
import { Check, Heart, ShoppingBag, Star } from "lucide-react";
import { Avatar, AvatarFallback, AvatarImage, Badge, Button, cn } from "@/components/ui";

export function Product() {
  const [added, setAdded] = useState(false);
  const [liked, setLiked] = useState(false);

  return (
    <div className="flex w-full max-w-xs flex-col overflow-hidden rounded-card border border-border bg-surface-raised">
      <div className="relative aspect-square overflow-hidden bg-surface">
        <img src="https://github.com/oyaksaile.png" alt="Aurora Hoodie" className="size-full object-cover" />
        <Badge variant="accent" className="absolute top-3 left-3">−20%</Badge>
        <button type="button" onClick={() => setLiked((v) => !v)} aria-pressed={liked} aria-label="Save to wishlist" className="absolute top-3 right-3 inline-flex size-9 items-center justify-center rounded-full bg-background/70 backdrop-blur-sm">
          <Heart className={cn("size-4", liked && "fill-danger text-danger")} />
        </button>
      </div>
      <div className="flex flex-col gap-3 p-5">
        <h3 className="font-display text-display-sm tracking-tight">Aurora Hoodie</h3>
        <div className="flex items-end justify-between">
          <span className="font-display text-display-sm tracking-tight">$72</span>
          <Badge variant="soft">In stock</Badge>
        </div>
        <Button className="w-full" onClick={() => setAdded(true)}>
          {added ? <><Check className="size-4" /> Added to cart</> : <><ShoppingBag className="size-4" /> Add to cart</>}
        </Button>
      </div>
    </div>
  );
}