# Mobile Screens

> A complete screen built from Avatar, Badge, Card, Chip and 7 more components.

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

## Code

```tsx
import * as React from "react";
import {
  Avatar,
  Badge,
  Card,
  Chip,
  Dock,
  EventCard,
  IconButton,
  ListRow,
  SearchField,
  Stagger,
  StatTile,
} from "@twentyfirstmedia/pro";
import { cardActs } from "./_workspace";

function Phone(p: { d: number; tab: number; children?: React.ReactNode }) {
  return (
    <div
      className="tf-in"
      style={
        {
          "--d": p.d + "ms",
          width: 360,
          maxWidth: "100%",
          height: 760,
          borderRadius: 44,
          background: "var(--frame)",
          padding: 10,
          boxSizing: "border-box",
          boxShadow: "var(--shadow-float)",
          flex: "none",
          scrollSnapAlign: "center",
        } as React.CSSProperties
      }
    >
      <div
        style={{
          height: "100%",
          borderRadius: 36,
          background: "var(--canvas)",
          overflow: "hidden",
          display: "flex",
          flexDirection: "column",
          position: "relative",
        }}
      >
        <div
          style={{
            display: "flex",
            justifyContent: "space-between",
            alignItems: "center",
            padding: "16px 20px 8px",
            font: "var(--type-title-weight) 13px var(--font-sans)",
          }}
        >
          <span>9:41</span>
          <span
            style={{
              width: 90,
              height: 24,
              borderRadius: "var(--radius-pill)",
              background: "var(--frame)",
            }}
          />
          <span>•••</span>
        </div>
        <div
          style={{
            flex: 1,
            overflow: "hidden",
            padding: "8px 16px 90px",
            display: "flex",
            flexDirection: "column",
            gap: 16,
          }}
        >
          {p.children}
        </div>
        <div
          style={{
            position: "absolute",
            left: 0,
            right: 0,
            bottom: 16,
            display: "flex",
            justifyContent: "center",
          }}
        >
          <Dock
            items={[
              {
                label: "Home",
                icon: "home",
                tint: p.tab === 0 ? "sky" : "plain",
              },
              {
                label: "Projects",
                icon: "grid",
                tint: p.tab === 1 ? "mint" : "plain",
              },
              {
                label: "Inbox",
                icon: "mail",
                tint: p.tab === 2 ? "lilac" : "plain",
              },
              {
                label: "New",
                icon: "plus",
              },
            ]}
          />
        </div>
      </div>
    </div>
  );
}
function App() {
  return (
    <div
      style={{
        display: "flex",
        gap: 28,
        justifyContent: "safe center",
        overflowX: "auto",
        scrollSnapType: "x mandatory",
        padding: "20px 0",
        background: "var(--canvas)",
        borderRadius: "var(--radius-xl)",
      }}
    >
      <Phone d={0} tab={0}>
        <div
          className="tf-row"
          style={{
            justifyContent: "space-between",
          }}
        >
          <div>
            <div className="tf-card-desc">Good morning</div>
            <div
              className="tf-header-title"
              style={{
                fontSize: 24,
              }}
            >
              Mara
            </div>
          </div>
          <Avatar name="Mara Ellis" size={40} />
        </div>
        <Stagger
          className="tf-grid"
          style={
            {
              "--cols": "1fr 1fr",
              "--cols-sm": "1fr 1fr",
              gap: 12,
            } as React.CSSProperties
          }
          delay={150}
        >
          <StatTile tint="sky" value="18" label="Projects" />
          <StatTile tint="butter" value="€48k" label="Revenue" />
          <StatTile tint="mint" value="11" label="Live" />
          <StatTile tint="placeholder" value="4" label="Review" />
        </Stagger>
        <div
          className="tf-in"
          style={
            {
              "--d": "400ms",
            } as React.CSSProperties
          }
        >
          <EventCard kind="meeting" label="Team sync" date="Today" time="10:30" actions={false}>
            Roadmap check-in with design and growth.
          </EventCard>
        </div>
      </Phone>
      <Phone d={120} tab={1}>
        <div
          className="tf-header-title"
          style={{
            fontSize: 24,
          }}
        >
          Projects
        </div>
        <div
          className="tf-row"
          style={{
            flexWrap: "nowrap",
          }}
        >
          <Chip selected>All</Chip>
          <Chip>Live</Chip>
          <Chip>Draft</Chip>
        </div>
        <Stagger className="tf-col" delay={250}>
          <Card
            tint="lilac"
            title="Pricing page refresh"
            description="New tiers and a comparison table."
            status={<Badge>86 viewing</Badge>}
            actions={[<IconButton key={1} icon="edit" label="Edit" size="sm" variant="solid" />]}
            style={{
              minHeight: 0,
            }}
          />
          <Card
            title="Quarterly onboarding"
            description="Welcome flow for new admins."
            status={<Badge tone="success">Live</Badge>}
            actions={cardActs()}
            style={{
              minHeight: 0,
            }}
          />
          <Card
            title="Partner referrals"
            description="Invites, payouts, tiers."
            status={<Badge tone="success">Live</Badge>}
            style={{
              minHeight: 0,
            }}
          />
        </Stagger>
      </Phone>
      <Phone d={240} tab={2}>
        <div
          className="tf-header-title"
          style={{
            fontSize: 24,
          }}
        >
          Inbox
        </div>
        <SearchField
          placeholder="Search messages"
          style={{
            minWidth: 0,
          }}
        />
        <Stagger className="tf-list" delay={350}>
          <ListRow
            avatar="Jonah Petit"
            title="Jonah Petit"
            subtitle="Move the review to Thursday?"
            meta="09:12"
            unread
          />
          <ListRow
            avatar="Northwind"
            title="Northwind"
            subtitle="Seat upgrade before renewal"
            meta="08:40"
            unread
          />
          <ListRow
            icon="bell"
            tint="butter"
            title="Billing"
            subtitle="Card ending 4417 expires"
            meta="Yest."
          />
          <ListRow avatar="Sun Lee" title="Sun Lee" subtitle="Payout file attached" meta="Yest." />
          <ListRow
            icon="flag"
            tint="blush"
            title="Incident #88"
            subtitle="Webhooks delayed in EU"
            meta="Sun"
          />
        </Stagger>
      </Phone>
    </div>
  );
}
export default function Preview() {
  return <App />;
}
```

## Props

### Avatar

| Prop | Type | Notes |
| --- | --- | --- |
| `name` | `string` | Required |
| `src?` | `string` |  |
| `size?` | `number` |  |
| `tint?` | `Tint \| "surface-sunken"` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Badge

| Prop | Type | Notes |
| --- | --- | --- |
| `tone?` | `Tone` |  |
| `dot?` | `boolean` |  |
| `live?` | `boolean` |  |
| `children?` | `ReactNode` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Card

| Prop | Type | Notes |
| --- | --- | --- |
| `title?` | `string` |  |
| `description?` | `string` |  |
| `meta?` | `string[]` |  |
| `corner?` | `ReactNode` |  |
| `status?` | `ReactNode` |  |
| `actions?` | `ReactNode[]` |  |
| `tint?` | `Tint \| "surface"` |  |
| `onClick?` | `() => void` |  |
| `children?` | `ReactNode` |  |
| `eyebrow?` | `ReactNode` |  |
| `surface?` | `Surface` | `"glass"`: translucent glass over what is behind (maps, 3D, video, photos); `"solid"` opts out inside a glass `UIProvider`. |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Chip

| Prop | Type | Notes |
| --- | --- | --- |
| `selected?` | `boolean` |  |
| `icon?` | `IconName` |  |
| `count?` | `number` |  |
| `onClick?` | `(e: MouseEvent<HTMLButtonElement>) => void` |  |
| `children?` | `ReactNode` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Dock

| Prop | Type | Notes |
| --- | --- | --- |
| `items` | `DockItem[]` | Required |
| `label?` | `string` |  |
| `surface?` | `Surface` | `"glass"`: translucent glass over what is behind (maps, 3D, video, photos); `"solid"` opts out inside a glass `UIProvider`. |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### EventCard

| Prop | Type | Notes |
| --- | --- | --- |
| `kind?` | `"meeting" \| "deadline" \| "review" \| "launch" \| "incident"` |  |
| `label` | `string` | Required |
| `date` | `string` | Required |
| `time` | `string` | Required |
| `tint?` | `Tint` |  |
| `icon?` | `IconName` |  |
| `actions?` | `boolean` |  |
| `children?` | `ReactNode` |  |
| `className?` | `string` | Extra class on the root element. |
| `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. |

### ListRow

| Prop | Type | Notes |
| --- | --- | --- |
| `avatar?` | `string` |  |
| `icon?` | `IconName` |  |
| `tint?` | `Tint \| "plain"` |  |
| `leading?` | `ReactNode` |  |
| `title` | `ReactNode` | Required |
| `subtitle?` | `ReactNode` |  |
| `meta?` | `ReactNode` |  |
| `trailing?` | `ReactNode` |  |
| `unread?` | `boolean` |  |
| `active?` | `boolean` |  |
| `onClick?` | `() => void` |  |
| `href?` | `string` | Makes the row a link (with `linkAs` for a router's link). |
| `linkAs?` | `ElementType` |  |
| `actions?` | `ReactNode` | Buttons of the row's own (rename, remove …), outside the clickable area so nothing interactive is nested. Without `onClick` and `href` the row is not interactive at all. |
| `titleLines?` | `number` | Lines the title may take before it ends in an ellipsis (default: it wraps freely). |
| `subtitleLines?` | `number` | Lines the subtitle may take (default 1); badges and facts wrap within them. |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### SearchField

| Prop | Type | Notes |
| --- | --- | --- |
| `placeholder?` | `string` |  |
| `label?` | `string` | Accessible name (default: the placeholder, or "Search"). |
| `shortcut?` | `string` | The key hint, e.g. "⌘K" (hidden on touch screens). |
| `bindShortcut?` | `boolean` | ⌘/Ctrl + the hint's last key (K for "⌘K") focuses the field, except while typing elsewhere. |
| `value?` | `string` |  |
| `defaultValue?` | `string` |  |
| `onChange?` | `(e: ChangeEvent<HTMLInputElement>) => void` |  |
| `onKeyDown?` | `(e: KeyboardEvent<HTMLInputElement>) => void` |  |
| `onFocus?` | `(e: FocusEvent<HTMLInputElement>) => void` |  |
| `onBlur?` | `(e: FocusEvent<HTMLInputElement>) => void` |  |
| `id?` | `string` |  |
| `name?` | `string` |  |
| `autoFocus?` | `boolean` |  |
| `inputProps?` | `InputHTMLAttributes<HTMLInputElement>` | For a search with suggestions (combobox): role, aria-controls, aria-activedescendant … |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Stagger

| Prop | Type | Notes |
| --- | --- | --- |
| `as?` | `string` |  |
| `className?` | `string` |  |
| `style?` | `CSSProperties` |  |
| `delay?` | `number` | Extra delay before the first child, in ms. |
| `reveal?` | `boolean` | Below the fold, wait until the group scrolls into view (default true). |
| `children?` | `ReactNode` |  |

### StatTile

| Prop | Type | Notes |
| --- | --- | --- |
| `value` | `ReactNode` | Required |
| `label` | `string` | Required |
| `tint?` | `Tint \| "surface" \| "placeholder"` |  |
| `delta?` | `string` |  |
| `delay?` | `number` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |
