A service's own page, top to bottom: its name and what it is in a sentence, then the parts a reader looks for before booking (what it is, who it is for, what happens step by step, what to bring, what comes after), each under its own heading; beside them a card that stays in view while the page scrolls (position: sticky, nothing animated) with the facts (how long, from what price, whether insurance covers it), the two actions and a line of small print. Zero JavaScript.
An examination of your teeth and gums, and a plan for what, if anything, comes next.
Who it is for
Every patient, about twice a year
Anyone new to the practice, at the first visit
What happens
We ask about your health and anything that hurts.
The dentist examines your teeth, gums and mouth.
X-rays, if they are due.
You leave with a plan and its price, in writing.
What to bring
The medicines you take
Your insurance card, if you have one
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
tone
density
Measured
JavaScript of its own
0 B
Uses Reveal and the shared runtime (1.9 kB raw, once per site). With those included: 3.0 kB raw.
CSS 13.5 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.
Browser support
Baseline · widely available
every browser. Elsewhere: nothing to fall back from.
Install Pro
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:
terminal
npx shadcn@latest add @moonarc-pro/service-detail
src/pages/index.astro
---import ServiceDetail from '@/components/moonarc/pro/ServiceDetail.astro';---<ServiceDetail title="Check-up" summary="An examination of your teeth and gums, and a plan for what, if anything, comes next." parts={[ { heading: 'What happens', steps: ['We ask about your health and anything that hurts.', 'The dentist examines your teeth and gums.', 'You leave with a plan and its price.'] }, ]} facts={[{ label: 'Takes', value: '30 minutes' }, { label: 'Costs', value: 'From $85' }]} primary={{ href: '/book/', label: 'Request this appointment' }}/>
Without a key the registry answers 401 with an upgrade message. Everything this section composes is free: reveal.
Props
Prop
Type
Default
Description
title
string
none
The service's name, the page's heading.
summary
string
none
What it is, in a sentence.
eyebrow
string
none
A short line above the title.
parts
{ heading; text?; list?; steps? }[]
none
The parts in order: paragraphs, a list of points, or numbered steps.
facts
{ label; value }[]
none
The card's facts.
primary
{ href; label }
none
The card's first action: book.
secondary
{ href; label }
none
The second: call, or see the fees.
fine
string
none
Small print under the actions.
back
{ href; label }
none
A link back to every service, over the title.
block
1 | 2 | 3 | 4 | 5
1
The card's colour block.
level
1 | 2
1
Heading level of the title; each part is one below.
tone
'surface' | 'muted' | 'ink' | 'brand'
unset (paints nothing)
The section's ground.
density
'compact' | 'cozy' | 'roomy'
'cozy'
Block padding ×0.8 / ×1 / ×1.25 through --ma-density.
Theming and overrides
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.
What a practice charges, as tables a reader can trust: one a group, each row a service, its price and a note, and where insurance applies a column that says in words whether it is covered, partly or not, with a mark that repeats it in shape.
What a practice does, by group: each group's name and a line about it on one side, its services on the other as rows, each with a line of what it is, how long it takes and what it costs from, and a link to its own page.