Motif
BackgroundsA drawn picture for a slot that has no photograph yet: a sun over the sea, a split gate, a flower, a grid of tiles, a phone going live, a plate set for dinner, waves under a moon or three shapes.
What one person sells, on a shelf: a course, a guide, a membership, prints or sessions. Each is a card with a cover drawn on a colour block (its name set large, a drawing in the corner, its kind on a band), then the kind and what it holds, the name, a line, the price and the way to buy it. A course lists its modules under a disclosure. One offer can be featured, twice as wide on a wide box. A pointer on a card lifts its cover off the shelf and leans it a little, and the buy button rises. Zero JavaScript.
Shop
Guide · 120 pages
How to plan, build and keep a small site going for years.
Guide · 48 pages
One colour to a whole palette, with the contrast checks.
Membership · yearly
A second letter each month and the guides as they come out.
$60 a year
Become a member (opens the shop)Live, from the library itself, at full size: scroll, hover, navigate away and back. The demo is never gated.
Variants · each row writes the attribute the prop writes; the section's own CSS answers
JavaScript of its own
0 B
CSS 18.9 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.
Browser support
Chrome 105 · Firefox 110 · Safari 16; needs container-units. Elsewhere: without container units the shelf stays one column and the headline and the names keep the page's text size.
The demo above is never gated; the source is. With a key in components.json (see account), the registry serves it like any other item:
npx shadcn@latest add @moonarc-pro/offer-shelf---
import OfferShelf from '@/components/moonarc/pro/OfferShelf.astro';
---
<OfferShelf
eyebrow="Work with me"
offers={[
{ name: 'The first thousand', kind: 'Course · 8 modules', price: '$149', note: 'Build a list of a thousand readers without ads.', href: 'https://shop.example.com/first-thousand', action: 'Enrol', includes: ['Choosing a topic', 'The welcome email', 'Your first ten issues'], featured: true },
{ name: 'Pricing in public', kind: 'Guide · 64 pages', price: '$19', href: '/shop/pricing-guide/', action: 'Buy the guide' },
{ name: 'One hour', kind: 'Session · 1 hour', price: '$90', href: '/book/', action: 'Book a session' },
]}
>
<span slot="headline">Everything I <em>sell</em></span>
</OfferShelf>Without a key the registry answers 401 with an upgrade message. Everything this section composes is free: motif.
| Prop | Type | Default | Description |
|---|---|---|---|
offers | { name, kind, href, price?, note?, action?, includes?, block?, art?, featured? }[] | none | The offers, in order. kind is "Course · 8 modules": the part before the first · goes on the cover's band and the whole of it in the mono face over the name. href is where it is bought or booked (your shop's page). action is the button's words ("Enrol"), the name when unset. includes is what it holds, a course's modules, listed under a disclosure with their count. block is the cover's colour (the first four in turn when unset); art is the cover's drawing (Motif), picked from the name when unset; featured makes the card twice as wide where the shelf has three columns. |
eyebrow | string | none | A short line above the heading. Slot "eyebrow" wins. |
headline | string | none | The heading. Slot "headline" wins and may hold an <em>, set in the emphasis face and colour. |
copy | string | none | A line under the heading. |
more | { href: string; label: string } | none | A link beside the heading, e.g. to the whole shop. |
includesLabel | string | 'What is inside' | The disclosure's words over a list of modules. |
externalLabel | string | '(opens the shop)' | What a screen reader hears after a link with a full http or https address, such as a shop on another site. |
level | 1 | 2 | 3 | 2 | Heading level of the section's heading; the offers' names are one below. |
tone | 'surface' | 'muted' | 'ink' | 'brand' | unset (paints nothing) | The section's ground; the covers keep their blocks on every tone. |
density | 'compact' | 'cozy' | 'roomy' | 'cozy' | Block padding ×0.8 / ×1 / ×1.25 through --ma-density. |
The section reads the theme contract (shadcn's variable names with the base tokens as fallback), so a pasted theme or one --ma-brand recolours it. Its class names are the override surface and stay stable; a named slot replaces the matching prop with your own markup.
.ma-shelf.ma-shelf__action.ma-shelf__art.ma-shelf__band.ma-shelf__body.ma-shelf__copy.ma-shelf__cover.ma-shelf__cover-name.ma-shelf__eyebrow.ma-shelf__foot.ma-shelf__head.ma-shelf__headline.ma-shelf__includes.ma-shelf__item.ma-shelf__kind.ma-shelf__list.ma-shelf__more.ma-shelf__n.ma-shelf__name.ma-shelf__note.ma-shelf__price.ma-shelf__titles
slot="eyebrow"slot="headline"
The hover states apply at once, without the spring on the cover or the rise of the button, and the disclosure's plus turns into a cross at once.
ClientRouterCSS only; nothing to rebind. An open disclosure is the browser's own state and resets with the page.
Replaces: Creator storefront pages (Gumroad, Teachable) · Product list sections (Tailwind Plus). See the migration table.
A drawn picture for a slot that has no photograph yet: a sun over the sea, a split gate, a flower, a grid of tiles, a phone going live, a plate set for dinner, waves under a moon or three shapes.
A product drawn flat, for a shop slot that has no photograph yet: 29 drawings in six sets (clothes, ceramics, coffee, skincare, furniture and objects, bakery).
Fixed packages with what they cost: each card names a package and who it suits, gives the price and what it is counted in (or "Price on request", for a business that quotes), lists what is included with a tick in a colour block, can carry a bonus line on that block, and ends in an action that asks about that package.
$0/yr
MIT, forever
$129/yr
One person, billed yearly
$349/yr
Up to 10 people, one invoice
Shows pricing plans side by side, with an annual/monthly toggle that rolls every price digit by digit to the other period with zero JavaScript (radios, :has and NumberFlow), the highlighted plan wearing a travelling border beam, cards revealed with a stagger.
The shop
32 cm
Price: $86
300 ml
Price: $24
16 cm
Price: $32
18 cm
Price: $64
90 ml
Price: $18
28 cm
Price: $72
A whole collection on one page, filtered by kind: the products as cards (each drawn on its plate with ProductArt, a hang tag for the price, the name as the card's link and a spec line in the mono face) in two, three or four columns by the box's width, and above them a row of filters with their counts (core FilterGrid: the cards that stay slide into place, and the URL keeps the filter).
Sample services
A name, a mark and the rules that hold them together, for a company that is new or has outgrown its look.
Deliverables
Sites designed and built by one team, from the first sketch to the code that ships.
Deliverables
Screens for an app or a tool, drawn with the people who will use them and handed over as working components.
Deliverables
The logo in motion, a product film, the interface itself: motion that says one thing each time.
Deliverables
What a studio or a freelancer does, as numbered rows at display size that open one at a time: each row names a service, and opens to a short text beside the list of deliverables.
Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown