# Storefront

> A complete screen built from Breadcrumb, Button, IconButton, Price and 6 more components.

Pattern · Pro kit · [Live page](https://ui.twentyfirst.media/pro/patterns/StorefrontScreen/)

## Code

```tsx
import * as React from "react";
import {
  Breadcrumb,
  Button,
  IconButton,
  Price,
  ProductArt,
  Rating,
  StockBadge,
  VariantPicker,
} from "@twentyfirstmedia/pro";

function App() {
  return (
    <div
      style={{
        borderRadius: "var(--radius-xl)",
        background: "var(--canvas)",
        overflow: "hidden",
        boxShadow: "inset 0 0 0 1px var(--line)",
      }}
    >
      <div
        style={{
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
          padding: "16px 24px",
          background: "var(--surface)",
          borderBottom: "1px solid var(--line)",
        }}
      >
        <b
          style={{
            font: "var(--type-title-weight) 20px/26px var(--font-sans)",
          }}
        >
          Halo Goods
        </b>
        <div className="tf-row">
          <IconButton icon="heart" label="Saved" />
          <Button icon="cart">Cart</Button>
        </div>
      </div>
      <div
        className="tf-grid"
        style={
          {
            "--cols": "minmax(0, 1.1fr) minmax(0, 1fr)",
            gap: 32,
            padding: 24,
          } as React.CSSProperties
        }
      >
        <div className="tf-col">
          <ProductArt kind="tee" tint="sky" />
          <div
            className="tf-grid"
            style={
              {
                "--cols": "repeat(4, 1fr)",
                "--cols-sm": "repeat(4, minmax(0, 1fr))",
                "--cols-md": "repeat(2, minmax(0, 1fr))",
                gap: 12,
              } as React.CSSProperties
            }
          >
            {(["sky", "mint", "butter", "lilac"] as const).map((t) => (
              <ProductArt key={t} kind="tee" tint={t} />
            ))}
          </div>
        </div>
        <div
          className="tf-col"
          style={{
            gap: 20,
          }}
        >
          <Breadcrumb items={["Shop", "Apparel", "Halo Cotton Tee"]} />
          <h1
            style={{
              margin: 0,
              font: "var(--type-title-weight) 28px/34px var(--font-sans)",
            }}
          >
            Halo Cotton Tee
          </h1>
          <Rating value={4.7} count={128} />
          <Price value={32} compareAt={39} size="lg" />
          <VariantPicker
            label="Colour"
            type="color"
            defaultValue="Sky"
            options={[
              {
                value: "Sky",
                label: "Sky",
                color: "#9fc3f0",
              },
              {
                value: "Ink",
                label: "Ink",
                color: "#1d1d1f",
              },
              {
                value: "Bone",
                label: "Bone",
                color: "#ece6d9",
              },
            ]}
          />
          <VariantPicker
            label="Size"
            defaultValue="M"
            options={[
              {
                value: "XS",
                label: "XS",
                disabled: true,
              },
              "S",
              "M",
              "L",
              "XL",
            ]}
          />
          <StockBadge stock={84} />
          <Button icon="cart">Add to cart · €32</Button>
        </div>
      </div>
    </div>
  );
}
export default App;
```

## Props

### Breadcrumb

| Prop | Type | Notes |
| --- | --- | --- |
| `items` | `(string \| Crumb)[]` | The trail; the last one is the current page. Items with `href` are links, with `onClick` buttons. |
| `linkAs?` | `ElementType` | The element links render as (default `a`), e.g. Next.js `Link`. |
| `onNavigate?` | `(index: number) => void` | Makes every crumb but the last a button; receives its index. |
| `label?` | `string` | Accessible name of the navigation (default "Breadcrumb"). |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Button

| Prop | Type | Notes |
| --- | --- | --- |
| `variant?` | `"primary" \| "secondary" \| "soft" \| "ghost" \| "danger" \| "danger-solid"` |  |
| `size?` | `"md" \| "sm"` |  |
| `icon?` | `IconName` |  |
| `iconRight?` | `IconName` |  |
| `disabled?` | `boolean` |  |
| `type?` | `"button" \| "submit" \| "reset"` |  |
| `onClick?` | `(e: MouseEvent<HTMLButtonElement>) => void` |  |
| `children?` | `ReactNode` |  |
| `asChild?` | `boolean` | Render the single child element (a link, a router Link) with the button's look instead of a <button>. |
| `id?` | `string` |  |
| `name?` | `string` | Form field name and value, sent with the form when this button submits it. |
| `value?` | `string` |  |
| `form?` | `string` | The id of the form this button belongs to, when it sits outside it. |
| `title?` | `string` |  |
| `tabIndex?` | `number` |  |
| `autoFocus?` | `boolean` |  |
| `aria-label?` | `string` |  |
| `aria-labelledby?` | `string` |  |
| `aria-describedby?` | `string` |  |
| `aria-expanded?` | `boolean \| "true" \| "false"` | For disclosure and menu triggers: whether what the button controls is open. |
| `aria-controls?` | `string` |  |
| `aria-haspopup?` | `boolean \| "true" \| "false" \| "menu" \| "listbox" \| "tree" \| "grid" \| "dialog"` |  |
| `aria-pressed?` | `boolean \| "true" \| "false" \| "mixed"` | For toggle buttons. |
| `aria-current?` | `boolean \| "true" \| "false" \| "page" \| "step" \| "location" \| "date" \| "time"` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Icon

| Prop | Type | Notes |
| --- | --- | --- |
| `name` | `IconName` | Required |
| `size?` | `number` |  |
| `label?` | `string` |  |
| `className?` | `string` |  |
| `style?` | `CSSProperties` | Inline style on the root element. |

### IconButton

| Prop | Type | Notes |
| --- | --- | --- |
| `icon` | `IconName` | Required |
| `label` | `string` | Accessible name and tooltip (`aria-label` and `title`); `aria-label` or `title` override them. |
| `variant?` | `"soft" \| "solid" \| "outline" \| "ghost"` |  |
| `size?` | `"sm" \| "md" \| "lg"` |  |
| `type?` | `"button" \| "submit" \| "reset"` |  |
| `onClick?` | `(e: MouseEvent<HTMLButtonElement>) => void` |  |
| `disabled?` | `boolean` |  |
| `asChild?` | `boolean` | Render the single child element (a link) with the button's look; the icon is placed inside it. |
| `children?` | `ReactNode` |  |
| `id?` | `string` |  |
| `name?` | `string` | Form field name and value, sent with the form when this button submits it. |
| `value?` | `string` |  |
| `form?` | `string` | The id of the form this button belongs to, when it sits outside it. |
| `title?` | `string` |  |
| `tabIndex?` | `number` |  |
| `autoFocus?` | `boolean` |  |
| `aria-label?` | `string` |  |
| `aria-labelledby?` | `string` |  |
| `aria-describedby?` | `string` |  |
| `aria-expanded?` | `boolean \| "true" \| "false"` | For disclosure and menu triggers: whether what the button controls is open. |
| `aria-controls?` | `string` |  |
| `aria-haspopup?` | `boolean \| "true" \| "false" \| "menu" \| "listbox" \| "tree" \| "grid" \| "dialog"` |  |
| `aria-pressed?` | `boolean \| "true" \| "false" \| "mixed"` | For toggle buttons. |
| `aria-current?` | `boolean \| "true" \| "false" \| "page" \| "step" \| "location" \| "date" \| "time"` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Price

| Prop | Type | Notes |
| --- | --- | --- |
| `value` | `number` | Required |
| `compareAt?` | `number` |  |
| `currency?` | `string` |  |
| `size?` | `"lg"` |  |
| `showSave?` | `boolean` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### ProductArt

| Prop | Type | Notes |
| --- | --- | --- |
| `kind` | `ArtKind` | Required |
| `tint?` | `Tint \| "plain"` |  |
| `size?` | `number` |  |
| `label?` | `string` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Rating

| Prop | Type | Notes |
| --- | --- | --- |
| `value` | `number` | Required |
| `count?` | `number` |  |
| `size?` | `number` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### StockBadge

| Prop | Type | Notes |
| --- | --- | --- |
| `stock` | `number` | Required |
| `low?` | `number` |  |
| `compact?` | `boolean` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### SubNav

| Prop | Type | Notes |
| --- | --- | --- |
| `eyebrow?` | `string` |  |
| `action?` | `{ label: string; icon?: IconName; onClick?: () => void }` |  |
| `items` | `SubNavItem[]` | Required |
| `current?` | `string` |  |
| `onPick?` | `(id: string) => void` |  |
| `children?` | `ReactNode` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### TopBar

| Prop | Type | Notes |
| --- | --- | --- |
| `brand?` | `ReactNode` | The product name, or a node (a logo mark with the name). |
| `tag?` | `string` |  |
| `items` | `NavItem[]` | Required |
| `active?` | `string` |  |
| `user?` | `{ name: string; email?: string }` |  |
| `onNavigate?` | `(label: string) => void` |  |
| `extra?` | `ReactNode` |  |
| `userOpen?` | `boolean` |  |
| `onUser?` | `() => void` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### VariantPicker

| Prop | Type | Notes |
| --- | --- | --- |
| `label` | `string` | Required |
| `type?` | `"color"` |  |
| `options` | `(string \| { value: string; label?: string; color?: string; disabled?: boolean })[]` | Required |
| `value?` | `string` |  |
| `defaultValue?` | `string` |  |
| `onChange?` | `(v: string) => void` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |
