Frame

A panel of rows set into a tray, with the tray's top strip carrying the title.

Dark previewReplay previewOpen in new tab

Deploys

Last 24 hours

main · a41c2e9

Production · 2 minutes ago

Ready

feature/sheet · 7d02b11

Preview · 9 minutes ago

Building

main · e5c90af

Production · 3 hours ago

Failed

docs/hero · 19bf3a4

Preview · 5 hours ago

Ready

Use it for a widget that is a list of rows under a title: deploys, settings, activity, service status. A panel of rows sits in a tray, and the tray's strip along the top carries the title, so the header needs no rule or background of its own.

band holds a bordered card in a thin muted band, for a widget that shows one figure or chart rather than a list. inset floats the panel in a recessed band and gives FrameFooter a place for actions. plain drops the tray, for a frame inside a card or another container that already draws an edge.

It has no state of its own and renders on the server. A row with href is a link and one with a @click listener is a button.

For a single surface with a heading and a body, use Card.

Installation

Copy the folder into components/ui, where you can edit it. It needs class-variance-authority and the cn helper, both already in a shadcn-vue project.

text
components/ui/frame/
├── Frame.vue
├── FrameHeader.vue … FrameFooter.vue
└── index.ts

Usage

vue
<script setup lang="ts">
import { Badge } from "@/components/ui/badge";
import {
  Frame,
  FrameAction,
  FrameActions,
  FrameContent,
  FrameDescription,
  FrameHeader,
  FrameMedia,
  FramePanel,
  FrameRow,
  FrameTitle,
} from "@/components/ui/frame";
</script>

<template>
  <Frame>
    <FrameHeader>
      <FrameTitle>Deploys</FrameTitle>
      <FrameAction>Last 24 hours</FrameAction>
    </FrameHeader>
    <FramePanel>
      <FrameRow>
        <FrameMedia><Icon name="lucide:rocket" /></FrameMedia>
        <FrameContent>
          <FrameTitle as="p">main · a41c2e9</FrameTitle>
          <FrameDescription>Production · 2 minutes ago</FrameDescription>
        </FrameContent>
        <FrameActions><Badge>Ready</Badge></FrameActions>
      </FrameRow>
    </FramePanel>
  </Frame>
</template>

Composition

<Frame>
  <FrameHeader>
    <FrameTitle />
    <FrameDescription />
    <FrameAction />
  </FrameHeader>
  <FramePanel>
    <FrameSection>
      <FrameRow>
        <FrameMedia />
        <FrameContent>
          <FrameTitle />
          <FrameDescription />
        </FrameContent>
        <FrameActions />
      </FrameRow>
    </FrameSection>
  </FramePanel>
  <FrameFooter />
</Frame>
  • FrameHeader: the strip above the panel: a title, an optional description, and FrameAction on the trailing side.
  • FrameTitle: a quiet caption in the header. Inside FrameContent it is the row's subject instead: full colour, and cut to one line. Use as="p" there.
  • FrameDescription: a line under a title, in the header or in a row.
  • FrameAction: the header's trailing side: a short status, a link, a small button.
  • FramePanel: the card the rows sit on. It draws the hairline between its children, except under band, where it holds one figure and draws none.
  • FrameRow: one line of the panel. href makes it a link, @click a button, and chevron marks one that leads somewhere.
  • FrameMedia: a row's leading icon or avatar. It keeps its size.
  • FrameContent: the middle of a row. It takes the space left and can shrink past its content, so a long line truncates instead of pushing the actions off the edge.
  • FrameActions: a row's trailing side: a badge, a value, a switch. It keeps its size.
  • FrameSection: a titled group of rows, for a panel holding more than one.
  • FrameFooter: the strip below the panel. Under inset its children become equal-width pills.

Band

variant="band" holds a bordered card in a thin muted band whose top strip names what is inside. It suits a widget with one thing to show: here a KPI and its chart.

Dark previewReplay previewOpen in new tab

Sign-ups

Last 14 days
New workspaces
1,288

Inset

The panel floats in a recessed band, and the band below it holds the footer's actions as equal pills.

Dark previewReplay previewOpen in new tab

Backups

Last run 02:00

Back up every night

At 02:00, while nobody is working

Keep copies for

Older ones are removed

30 days

Sections

FrameSection groups rows under a small heading, and divides its own rows the way the panel does.

Dark previewReplay previewOpen in new tab

Activity

All projects

Today

IF

Ife commented on Checkout redesign

10:42
TM

Tomás moved Search ranking to Blocked

09:15

Yesterday

HK

Hana shipped Onboarding emails

17:30
NH

Noor invited Pavel to Payments

11:02

Inside a card

plain drops the tray, so a frame inside a card has one edge rather than two. Hold it 8px off the card's edge and give the panel the card's radius less that gap.

Dark previewReplay previewOpen in new tab

System status

Checked a minute ago

API

Frankfurt

Operational

Dashboard

Frankfurt

Operational

Webhooks

Dublin

Degraded

API Reference

Frame

PropTypeDefaultDescription
variant"default" | "band" | "inset" | "plain""default"default sets the panel in a tray. band holds a bordered card in a thin muted band, for a widget showing one figure or chart, and its panel draws no rule between its children. inset floats the panel in a recessed band and gives FrameFooter somewhere to sit. plain drops the shell, for a frame inside a container that already draws its own edge.

FrameTitle

PropTypeDefaultDescription
as"h2" | "h3" | "h4" | "p""h3"The heading level in a header. A row title is always a p.

FrameRow

PropTypeDefaultDescription
hrefstring—Make the row a link to this address. Rendered as NuxtLink.
targetstring—Where a link row opens, as on an <a>.
relstring—The relationship of a link row's target, as on an <a>.
chevronbooleanfalseA trailing chevron, for a row that leads somewhere.
EventDescription
clickA @click listener makes the row a button. Ignored when href is set.

FrameSection

PropTypeDefaultDescription
titlestring—The heading over the section's rows.
SlotDescription
titleMarkup for the heading, in place of the title prop.

Every part also takes the attributes of the element it renders, and a class.

Notes

The panel draws a hairline between each of its children, so rows never need to know whether they come first.

Under band and inset the panel's corners are the shell's less the band, so the two curves stay concentric.

The variant reaches the parts through a data attribute and CSS rather than provide/inject, which is what keeps the frame free of client state.