Skip to content

Components / Sections

Service Detail

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.

Live demo

Every service

Check-up

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

  1. We ask about your health and anything that hurts.
  2. The dentist examines your teeth, gums and mouth.
  3. X-rays, if they are due.
  4. 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

PropTypeDefaultDescription
titlestringnoneThe service's name, the page's heading.
summarystringnoneWhat it is, in a sentence.
eyebrowstringnoneA short line above the title.
parts{ heading; text?; list?; steps? }[]noneThe parts in order: paragraphs, a list of points, or numbered steps.
facts{ label; value }[]noneThe card's facts.
primary{ href; label }noneThe card's first action: book.
secondary{ href; label }noneThe second: call, or see the fees.
finestringnoneSmall print under the actions.
back{ href; label }noneA link back to every service, over the title.
block1 | 2 | 3 | 4 | 51The card's colour block.
level1 | 21Heading 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.

Classes

.ma-svc.ma-svc__actions.ma-svc__aside.ma-svc__back.ma-svc__card.ma-svc__eyebrow.ma-svc__facts.ma-svc__fine.ma-svc__heading.ma-svc__inner.ma-svc__list.ma-svc__main.ma-svc__p.ma-svc__part.ma-svc__primary.ma-svc__secondary.ma-svc__steps.ma-svc__summary.ma-svc__title

Slots

none

Reduced motion

The parts arrive at once; the card is sticky either way, which is a position and not a motion.

With ClientRouter

CSS and Reveal.

Why it is built this way

Replaces: Service page templates. See the migration table.

Contact · sample page

Write to the team.

Sample channels on the left. The form on the right is real and sends nothing here: fill it in and watch the button.

Email (sample)
hello@studio.example
Phone (sample)
+1 555 0100
Office (sample)
1 Sample Street, Springfield

Demo: nothing is sent.

A contact section in two columns: the ways to reach you (email, phone, office) on one side, a form that really sends on the other.

Chrome 135 · not Firefox · Safari 27click

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.

every browserscroll

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.

every browserscroll

Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown