Blog//7 min read
Dental website template for Next.js
A dental website template for Next.js built from ten blocks: hero with live rating, insurance logo cloud, team grid, booking slots, plus a HIPAA note.
MSMarco Schulz, founder of Facade UI
A dental practice website has one job: get a nervous person with a toothache to book an appointment. Everything on the page either reduces the nervousness or shortens the path to the booking, and anything that does neither should go. This is the block order I use when I build a dental website template in Next.js, why each block is there, and the one compliance question every dental client asks about their forms.
The questions a patient brings
I write the patient's questions down before I touch a layout. For a dentist they are consistent:
- Do you take my insurance?
- Who is the dentist, and will it hurt?
- Are other people happy with you?
- What does the work look like afterwards?
- Can I get in soon, and how do I book?
Insurance comes first because it is a hard filter. The dentist comes second because dental anxiety is real and a face with a bio beats any stock photo. Booking is last on the list but must be reachable from the first screen, which is what the hero's call to action is for.
The ten blocks in order
Here is the full page, top to bottom, with the Facade block for each section:
- Hero, local business: headline, live rating, rotating service words, book and call buttons.
- Logo cloud: accepted insurance plans as a wordmark grid.
- Team grid: the dentists and hygienists with bios.
- Features bento: six cells for the things patients ask about, from same day crowns to sedation.
- Testimonials wall: Google reviews with names and the service they came in for.
- Before and after slider: smile makeovers, whitening, veneers.
- FAQ section: insurance, pain, first visit, with FAQPage schema.
- Booking slots: the appointment picker.
- Contact section: form, address, hours, map.
- Footer, big: NAP details, hours, links.
The hero: rating, rotating words, two buttons
The local business hero puts the rating, review count and source next to the headline, so the proof is on screen before anyone scrolls. The headline has a rotating word that swaps every few seconds. For a dentist I pick those words carefully; this is where the anxious patient decides whether the practice is for them.
import { HeroLocalBusiness } from "@/components/facade/hero-local-business";
<HeroLocalBusiness
rating={4.9}
reviewCount="640+"
eyebrowSuffix="reviews on Google"
headlinePrefix="Gentle dentistry for"
rotatingWords={["families", "kids", "nervous patients", "new smiles"]}
headlineSuffix="in Round Rock"
description="Same week appointments, most PPO plans accepted, and a team that explains every step before it happens."
primaryCta={{ label: "Book an appointment", href: "#book" }}
phone={{ label: "(512) 555-0142", href: "tel:5125550142" }}
appointment={{ service: "New patient exam", date: "Thursday", time: "2:30 PM", address: "Round Rock" }}
review={{ name: "Dana M.", neighborhood: "Brushy Creek", quote: "First dentist my son did not cry at." }}
technician={{ name: "Dr. Priya Shah", eta: "Next opening Thu", vehicle: "New patient exam" }}
interval={2600}
/>The three floating cards were designed for home services: an appointment, a review, a technician on the way. For a dentist the appointment card shows the next slot, the review card a short quote, and the technician card the dentist with the next opening. The props are plain strings, so the component does not care what story they tell.
Insurance logos right under the hero
"Do you take my insurance?" is answered by the Logo cloud directly under the hero. Each item is an insurer's name as a wordmark with an optional detail line revealed on hover. I use the detail for the plan types accepted, the follow up question a patient would otherwise call about.
import { LogoCloud } from "@/components/facade/logo-cloud";
<LogoCloud
eyebrow="Insurance"
title="We accept most PPO plans"
description="In network with the plans below. Out of network? We file the claim for you."
variant="grid"
logos={[
{ name: "Delta Dental", detail: "PPO and Premier" },
{ name: "Cigna", detail: "DPPO" },
{ name: "Aetna", detail: "PPO" },
{ name: "MetLife", detail: "PDP Plus" },
{ name: "Guardian", detail: "PPO" },
]}
/>Use the insurers' names as text unless the practice has permission to display their marks; the component renders a wordmark when no logo node is passed. And keep the list truthful. A patient who learns at the front desk that the practice dropped their plan last year is a one star review in the making.
The team grid
A dentist's face does more than a treatment list. The Team grid shows each person with a role, a bio revealed on hover and optional social links. Photos go in imageSrc; without one the component renders a gradient initials avatar instead of a gray silhouette.
import { TeamGrid } from "@/components/facade/team-grid";
<TeamGrid
eyebrow="Your care team"
heading="The people behind the chair"
members={[
{ name: "Dr. Priya Shah", role: "General dentist", bio: "Explains every x-ray before it is taken.", imageSrc: "/team/priya.jpg" },
{ name: "Dr. Marcus Webb", role: "Pediatric dentist", bio: "Kids come back asking for him.", imageSrc: "/team/marcus.jpg" },
{ name: "Elena Ruiz", role: "Lead hygienist", bio: "Gentle cleanings, honest advice." },
]}
showHiringCard={false}
/>Bios should say what the person is like to be treated by. Credentials go second, reassurance first.
Booking without a backend
The Booking slots component renders a 14 day date strip, groups the day's times into morning, afternoon and evening, greys out taken and past slots, and shows a summary before the visitor confirms. It is a Pro item; it installs from the private @facade registry once the license key is in components.json (see the Pro setup in the installation docs).
import { BookingSlots, type BookingSelection } from "@/components/facade/booking-slots";
<BookingSlots
serviceName="New patient exam and cleaning"
durationMinutes={60}
priceLabel="Covered by most plans"
days={14}
closedWeekdays={[0]}
getSlots={() => ["08:00", "08:30", "09:00", "10:00", "11:00", "13:00", "14:00", "15:00", "16:00"]}
isSlotDisabled={(date, time) => takenSlots.has(`${date.toDateString()} ${time}`)}
confirmLabel="Request this time"
onConfirm={(selection: BookingSelection) => fetch("/api/book", { method: "POST", body: JSON.stringify(selection) })}
/>getSlots and isSlotDisabled connect it to reality. With a scheduling system, fetch the taken slots server side and pass a Set down. Without one, skip isSlotDisabled, label the button "Request this time" and let the front desk confirm. onConfirm hands you the date as a YYYY-MM-DD string and the time; what happens next is your decision.
Installing the free blocks is one command each:
npx shadcn@latest add https://facadeui.com/r/hero-local-business.json https://facadeui.com/r/logo-cloud.json https://facadeui.com/r/team-grid.jsonA note on HIPAA and forms
Every dental client asks this and I am not the person to give a legal answer; here is how I treat it technically. HIPAA covers protected health information handled by covered entities, and a dental practice is one. A free text field asking "What brings you in?" invites health information onto your server, your email provider and your form tool.
My rules for the contact and booking forms on a dental site:
- Collect the minimum: name, phone, email, requested time, and a service picked from a short list. No free text about symptoms on the public site.
- Do not send form contents to a marketing tool or a shared inbox. Post them to the practice's own system or to a form provider that will sign a business associate agreement.
- Say on the form what not to type. One line: "Please do not include health details here. We will ask when we call."
- Do not fire marketing pixels on the booking confirmation page.
Whether a given vendor needs a BAA, and whether a field counts as PHI, is a question for the practice's compliance advisor. Build the site so the answer can be yes without a redesign.
FAQ
Can I use the booking picker with an existing scheduler like Dentrix or Curve?
Yes, if the scheduler exposes booked times through an API or export. Fetch them server side, pass the set into isSlotDisabled, post the confirmation back. The scheduler stays the source of truth.
Should a dental site have a blog?
Only if someone will write it. An abandoned blog reads as neglect. The FAQ section with schema covers most of what a blog would and stays current because it is part of the page.
How many before and after images are enough?
Six to nine, with written permission for each. One strong slider, the rest in a gallery. Label the treatment and the time between photos; unlabeled transformations read as stock imagery.
Components in this article
- Hero, local businessFreeHome services hero with live rating, rotating service words and floating appointment, review and ETA cards.
- Booking slotsProAppointment picker with a 14 day date strip, grouped time slots, summary and confirm callback.
- Team gridFreeTeam members grid with gradient initials avatars, bio reveal on hover, social links and a hiring card.
- Logo cloudFreeTrusted by wordmark grid with hover reveal of client outcomes and an optional two row marquee mode.