Autoplay Feature Tabs
Autoplay feature tabs for React: tabs advance on a filling progress bar, pause on hover, and show a phone screen built in code.
- Feature Sections
- Healthcare
- React only · 0 deps
- 1 file
Browser support · All evergreen browsers. Description reveal uses grid-template-rows interpolation (Chrome 107+, Safari 16+, Firefox 66+).
Install. One command, or copy the file.
$
npx shadcn@latest add https://freedesignmd.com/r/autoplay-feature-tabs.json- Byte-identical to the preview
- No Tailwind or config needed
- SSR-safe · keyboard accessible
- Respects reduced motion & transparency
components/features/autoplay-feature-tabs.tsx
1/**2 * AutoplayFeatureTabs, feature tabs that advance on their own with a filling3 * progress bar, each tab showing a phone screen built in code. Shown for a4 * family GP practice's patient app: booking, test results, medication and5 * messages.6 *7 * Props: eyebrow, title, lede, tabs ({ id, label, blurb, screen }), interval (ms).8 * `screen` is any ReactNode rendered inside the phone frame.9 * CSS vars: --fd-aft-bg, --fd-aft-ink, --fd-aft-clay, --fd-aft-sage.10 *11 * Browser support: all evergreen browsers.12 * Motion: one rAF clock drives the progress bar (transform: scaleX). It pauses13 * on hover, on keyboard focus inside the section, when off-screen and when the14 * tab is hidden. Screens crossfade with a small lift.15 * Accessibility: WAI-ARIA tabs (role="tablist" / "tab" / "tabpanel"), arrow16 * keys, Home and End move between tabs; a visible pause button stops the17 * rotation (WCAG 2.2.2). Phone UI inside the panel is real text.18 * Reduced motion: no auto-advance (the pause button is hidden), screens swap19 * instantly.20 */21import { useEffect, useId, useRef, useState } from "react";22import type { KeyboardEvent as ReactKeyboardEvent, ReactNode } from "react";23 24export interface AftTab {25 id: string;26 label: string;27 blurb: string;28 screen: ReactNode;29}30 31export interface AutoplayFeatureTabsProps {32 eyebrow?: ReactNode;33 title?: ReactNode;34 lede?: ReactNode;35 tabs?: AftTab[];36 interval?: number;37 className?: string;38}39 40/* ───────────── phone screens ───────────── */41function BookScreen() {42 const days = [["Mon", 3], ["Tue", 4], ["Wed", 5], ["Thu", 6], ["Fri", 7]] as const;43 const slots = ["08:20", "09:10", "10:40", "11:50", "14:30", "16:00"];44 return (45 <div className="fd-aft-scr">46 <p className="fd-aft-s-k">Book</p>47 <h4 className="fd-aft-s-h">See Dr Okafor</h4>48 <div className="fd-aft-days">49 {days.map(([d, n], i) => (50 <span key={d} data-on={i === 1 ? "" : undefined}><small>{d}</small>{n}</span>51 ))}52 </div>53 <p className="fd-aft-s-l">Tuesday 4 November</p>54 <div className="fd-aft-slots">55 {slots.map((s, i) => <span key={s} data-on={i === 2 ? "" : undefined} data-gone={i === 0 || i === 4 ? "" : undefined}>{s}</span>)}56 </div>57 <div className="fd-aft-doc">58 <img src="/img/people/p9.webp" alt="" width={40} height={40} loading="lazy" />59 <span><b>Dr Amara Okafor</b><small>GP, 14 years at Wren</small></span>60 </div>61 <div className="fd-aft-pill">In person · Room 3 · 10 min</div>62 <span className="fd-aft-cta">Book 10:40</span>63 </div>64 );65}66 67function ResultsScreen() {68 const r = [69 { n: "Ferritin", v: "18", u: "µg/L", lo: 30, hi: 400, max: 450, flag: "Low" },70 { n: "Vitamin D", v: "62", u: "nmol/L", lo: 50, hi: 125, max: 160 },71 { n: "HbA1c", v: "36", u: "mmol/mol", lo: 20, hi: 41, max: 60 },72 ];73 return (74 <div className="fd-aft-scr">75 <p className="fd-aft-s-k">Results</p>76 <h4 className="fd-aft-s-h">Blood test, 28 Oct</h4>77 <div className="fd-aft-res">78 {r.map((x) => {79 const v = parseFloat(x.v);80 return (81 <div key={x.n} className="fd-aft-res-r">82 <div className="fd-aft-res-t"><span>{x.n}</span>{x.flag && <em>{x.flag}</em>}<b>{x.v}<small> {x.u}</small></b></div>83 <div className="fd-aft-range">84 <i className="fd-aft-ok" style={{ left: `${(x.lo / x.max) * 100}%`, width: `${((x.hi - x.lo) / x.max) * 100}%` }} />85 <i className="fd-aft-dot" data-flag={x.flag ? "" : undefined} style={{ left: `${(v / x.max) * 100}%` }} />86 </div>87 </div>88 );89 })}90 </div>91 <div className="fd-aft-note">92 <img src="/img/people/p9.webp" alt="" width={24} height={24} loading="lazy" />93 <p>“Iron is a little low. Nothing urgent, we'll talk it through on Tuesday.” <small>Dr Okafor</small></p>94 </div>95 </div>96 );97}98 99function MedsScreen() {100 const m = [101 { t: "07:00", n: "Levothyroxine", d: "50 µg, before breakfast", s: "taken" },102 { t: "13:00", n: "Ferrous fumarate", d: "210 mg, with orange juice", s: "due" },103 { t: "20:00", n: "Vitamin D3", d: "1,000 IU", s: "later" },104 ];105 return (106 <div className="fd-aft-scr">107 <p className="fd-aft-s-k">Medication</p>108 <h4 className="fd-aft-s-h">Today, 3 doses</h4>109 <ol className="fd-aft-meds">110 {m.map((x) => (111 <li key={x.n} data-s={x.s}>112 <span className="fd-aft-med-t">{x.t}</span>113 <span className="fd-aft-med-n"><b>{x.n}</b><small>{x.d}</small></span>114 <span className="fd-aft-med-c" aria-label={x.s}>{x.s === "taken" && <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M2.5 6.2 5 8.6l4.5-5" /></svg>}</span>115 </li>116 ))}117 </ol>118 <div className="fd-aft-refill">119 <div className="fd-aft-refill-bar"><i /></div>120 <p><b>12 tablets left.</b> Repeat sent to Hyde Park Pharmacy, ready Thursday.</p>121 </div>122 </div>123 );124}125 126function MessagesScreen() {127 return (128 <div className="fd-aft-scr fd-aft-chat">129 <div className="fd-aft-chat-h">130 <img src="/img/people/p6.webp" alt="" width={32} height={32} loading="lazy" />131 <span><b>Priya, practice nurse</b><small>Replies within 2 working hours</small></span>132 </div>133 <div className="fd-aft-msgs">134 <p className="fd-aft-me" style={{ ["--i" as string]: 0 }}>Hi Priya, the rash on Leo's arm is back. Photo attached. Should I bring him in?</p>135 <div className="fd-aft-photo" style={{ ["--i" as string]: 1 }} aria-hidden="true" />136 <p className="fd-aft-them" style={{ ["--i" as string]: 2 }}>Thanks Sofia. That looks like his eczema flaring, not an infection.</p>137 <p className="fd-aft-them" style={{ ["--i" as string]: 3 }}>Use the 1% cream twice a day for 5 days. If it's not calmer by Monday, I've held 09:10 for you.</p>138 <span className="fd-aft-typing" style={{ ["--i" as string]: 4 }} aria-label="Priya is typing"><i /><i /><i /></span>139 </div>140 <div className="fd-aft-compose">Message Priya</div>141 </div>142 );143}144 145const DEFAULT_TABS: AftTab[] = [146 { id: "book", label: "Book", blurb: "Pick the doctor you saw last time, at a time that works around school pickup.", screen: <BookScreen /> },147 { id: "results", label: "Results", blurb: "Test results land with a note from your GP, in plain English, the same day they do.", screen: <ResultsScreen /> },148 { id: "meds", label: "Medication", blurb: "Reminders that know iron and thyroxine shouldn't share a breakfast. Repeats reorder themselves.", screen: <MedsScreen /> },149 { id: "messages", label: "Messages", blurb: "Send a photo to a nurse instead of queuing on the phone at 08:00.", screen: <MessagesScreen /> },150];151 152export function AutoplayFeatureTabs({153 eyebrow = "The Wren app",154 title = <>The waiting room, <em>minus the waiting.</em></>,155 lede = "Four things our 11,400 patients used to ring reception for. Now they take about a minute each.",156 tabs = DEFAULT_TABS,157 interval = 6500,158 className,159}: AutoplayFeatureTabsProps) {160 const [active, setActive] = useState(0);161 const [hover, setHover] = useState(false);162 const [focus, setFocus] = useState(false);163 const [paused, setPaused] = useState(false);164 const [reduced, setReduced] = useState(false);165 const [visible, setVisible] = useState(true);166 const root = useRef<HTMLElement>(null);167 const bars = useRef<(HTMLSpanElement | null)[]>([]);168 const elapsed = useRef(0);169 const uid = useId();170 const stopped = paused || reduced;171 const halted = stopped || hover || focus || !visible;172 173 useEffect(() => {174 const m = window.matchMedia("(prefers-reduced-motion: reduce)");175 setReduced(m.matches);176 const on = () => setReduced(m.matches);177 m.addEventListener("change", on);178 const el = root.current;179 let io: IntersectionObserver | undefined;180 if (el) { io = new IntersectionObserver(([e]) => setVisible(e.isIntersecting), { threshold: 0.2 }); io.observe(el); }181 return () => { m.removeEventListener("change", on); io?.disconnect(); };182 }, []);183 184 useEffect(() => { elapsed.current = 0; bars.current.forEach((b, i) => b && (b.style.transform = `scaleX(${i < active && !stopped ? 1 : 0})`)); }, [active, stopped]);185 186 useEffect(() => {187 if (halted) return;188 let raf = 0, last = performance.now();189 const tick = (t: number) => {190 if (document.hidden) { last = t; raf = requestAnimationFrame(tick); return; }191 elapsed.current += t - last; last = t;192 const p = Math.min(1, elapsed.current / interval);193 const b = bars.current[active];194 if (b) b.style.transform = `scaleX(${p})`;195 if (p >= 1) { setActive((a) => (a + 1) % tabs.length); return; }196 raf = requestAnimationFrame(tick);197 };198 raf = requestAnimationFrame(tick);199 return () => cancelAnimationFrame(raf);200 }, [halted, active, interval, tabs.length]);201 202 const select = (i: number, f = false) => {203 setActive(i);204 if (f) document.getElementById(`${uid}-t-${i}`)?.focus();205 };206 const onKey = (e: ReactKeyboardEvent, i: number) => {207 const n = tabs.length;208 const map: Record<string, number> = { ArrowDown: (i + 1) % n, ArrowRight: (i + 1) % n, ArrowUp: (i - 1 + n) % n, ArrowLeft: (i - 1 + n) % n, Home: 0, End: n - 1 };209 if (e.key in map) { e.preventDefault(); select(map[e.key], true); }210 };211 212 return (213 <section214 ref={root}215 className={`fd-aft${className ? " " + className : ""}`}216 aria-labelledby={`${uid}-h`}217 onPointerEnter={(e) => e.pointerType === "mouse" && setHover(true)}218 onPointerLeave={() => setHover(false)}219 onFocus={(e) => { if ((e.target as HTMLElement).matches?.(":focus-visible")) setFocus(true); }}220 onBlur={(e) => { if (!e.currentTarget.contains(e.relatedTarget as Node)) setFocus(false); }}221 >222 <style>{css}</style>223 <div className="fd-aft-wrap">224 <div className="fd-aft-copy">225 <p className="fd-aft-eyebrow">{eyebrow}</p>226 <h2 id={`${uid}-h`} className="fd-aft-title">{title}</h2>227 <p className="fd-aft-lede">{lede}</p>228 <div className="fd-aft-tabs" role="tablist" aria-orientation="vertical" aria-label="App features">229 {tabs.map((t, i) => (230 <button231 key={t.id}232 id={`${uid}-t-${i}`}233 type="button"234 role="tab"235 aria-selected={i === active}236 aria-controls={`${uid}-p`}237 tabIndex={i === active ? 0 : -1}238 className="fd-aft-tab"239 onClick={() => select(i)}240 onKeyDown={(e) => onKey(e, i)}241 >242 <span className="fd-aft-tab-n">0{i + 1}</span>243 <span className="fd-aft-tab-b">244 <span className="fd-aft-tab-l">{t.label}</span>245 <span className="fd-aft-tab-d"><span>{t.blurb}</span></span>246 </span>247 <span className="fd-aft-prog" aria-hidden="true"><span ref={(el) => { bars.current[i] = el; }} /></span>248 </button>249 ))}250 </div>251 {!reduced && (252 <button type="button" className="fd-aft-pause" aria-pressed={paused} onClick={() => setPaused((p) => !p)}>253 {paused254 ? <><svg width="10" height="10" viewBox="0 0 10 10" aria-hidden="true"><path d="M2 1l7 4-7 4z" fill="currentColor" /></svg>Play tour</>255 : <><svg width="10" height="10" viewBox="0 0 10 10" aria-hidden="true"><path d="M2 1h2v8H2zM6 1h2v8H6z" fill="currentColor" /></svg>Pause tour</>}256 </button>257 )}258 </div>259 <div className="fd-aft-stage">260 <div className="fd-aft-arch" aria-hidden="true" />261 <div className="fd-aft-phone">262 <div className="fd-aft-island" aria-hidden="true" />263 <div className="fd-aft-status" aria-hidden="true"><span>9:41</span><span className="fd-aft-sig"><i /><i /><i /><i /></span></div>264 <div id={`${uid}-p`} role="tabpanel" aria-labelledby={`${uid}-t-${active}`} className="fd-aft-panel">265 {tabs.map((t, i) => (266 <div key={t.id} className="fd-aft-screen" data-on={i === active ? "" : undefined} hidden={i !== active && reduced ? true : undefined} aria-hidden={i !== active}>267 {t.screen}268 </div>269 ))}270 </div>271 <div className="fd-aft-home" aria-hidden="true" />272 </div>273 <p className="fd-aft-caption" aria-hidden="true">{String(active + 1).padStart(2, "0")} {tabs[active].label}</p>274 </div>275 </div>276 </section>277 );278}279 280const css = `281:where(.fd-aft,.fd-aft *){box-sizing:border-box;margin:0;padding:0;font:inherit;letter-spacing:inherit;color:inherit;text-decoration:none;list-style:none;border:0;background:none}282:where(.fd-aft,.fd-aft *)::before,:where(.fd-aft,.fd-aft *)::after{box-sizing:border-box}283:where(.fd-aft svg,.fd-aft img){display:block;flex-shrink:0}284:where(.fd-aft button){appearance:none;-webkit-appearance:none;outline:0;border-radius:0;cursor:pointer;text-align:left}285.fd-aft{286 --fd-ease-out:cubic-bezier(.22,1,.36,1);--fd-ease-in-out:cubic-bezier(.65,0,.35,1);--fd-spring:cubic-bezier(.34,1.56,.64,1);287 --fd-dur-1:120ms;--fd-dur-2:240ms;--fd-dur-3:420ms;--fd-dur-4:700ms;288 --fd-aft-bg:#F4ECE1;--fd-aft-ink:#2B1C16;--fd-aft-mut:#7A6457;--fd-aft-line:rgba(43,28,22,.12);--fd-aft-clay:#B5532E;--fd-aft-sage:#6F8466;--fd-aft-cream:#FBF7F1;--fd-aft-blush:#EBD3C2;289 --fd-aft-serif:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Georgia,serif;290 --fd-aft-sans:ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif;291 display:block;width:100%;background:var(--fd-aft-bg);color:var(--fd-aft-ink);padding:clamp(56px,8vw,120px) clamp(16px,4vw,56px);292 font:400 15px/1.5 var(--fd-aft-sans);-webkit-font-smoothing:antialiased}293.fd-aft-wrap{max-width:1180px;margin:0 auto;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(32px,6vw,96px);align-items:center}294.fd-aft-eyebrow{font:500 13px/18px var(--fd-aft-sans);letter-spacing:.14em;text-transform:uppercase;color:var(--fd-aft-clay)}295.fd-aft-title{margin-top:16px;font:400 clamp(2.5rem,5vw,4.25rem)/1.02 var(--fd-aft-serif);letter-spacing:-.025em;color:var(--fd-aft-ink)}296.fd-aft-title em{font-style:italic;color:var(--fd-aft-clay)}297.fd-aft-lede{margin-top:20px;max-width:40ch;font:400 17px/1.55 var(--fd-aft-sans);color:var(--fd-aft-mut)}298.fd-aft-tabs{margin-top:40px;display:flex;flex-direction:column;border-top:1px solid var(--fd-aft-line)}299.fd-aft-tab{position:relative;display:grid;grid-template-columns:40px minmax(0,1fr);gap:8px;padding:18px 4px 20px;border-bottom:1px solid var(--fd-aft-line);color:var(--fd-aft-ink);border-radius:2px;transition:background-color var(--fd-dur-2)}300.fd-aft-tab:hover{background:rgba(255,255,255,.35)}301.fd-aft-tab:focus-visible{outline:2px solid rgba(181,83,46,.7);outline-offset:2px}302.fd-aft-tab-n{font:400 15px/26px var(--fd-aft-serif);font-style:italic;color:var(--fd-aft-mut)}303.fd-aft-tab-b{display:flex;flex-direction:column;min-width:0}304.fd-aft-tab-l{font:400 22px/26px var(--fd-aft-serif);letter-spacing:-.01em;color:var(--fd-aft-mut);transition:color var(--fd-dur-2)}305.fd-aft-tab-d{display:grid;grid-template-rows:0fr;opacity:0;font:400 15px/1.5 var(--fd-aft-sans);color:var(--fd-aft-mut);max-width:44ch;transition:grid-template-rows var(--fd-dur-3) var(--fd-ease-in-out),opacity var(--fd-dur-3) var(--fd-ease-out),margin var(--fd-dur-3) var(--fd-ease-in-out);overflow:hidden}306.fd-aft-tab-d>span{min-height:0;overflow:hidden}307.fd-aft-tab[aria-selected="true"] .fd-aft-tab-l{color:var(--fd-aft-ink)}308.fd-aft-tab[aria-selected="true"] .fd-aft-tab-n{color:var(--fd-aft-clay)}309.fd-aft-tab[aria-selected="true"] .fd-aft-tab-d{grid-template-rows:1fr;opacity:1;margin-top:6px}310.fd-aft-prog{position:absolute;left:0;right:0;bottom:-1px;height:2px;overflow:hidden}311.fd-aft-prog span{display:block;height:100%;background:var(--fd-aft-clay);transform-origin:left;transform:scaleX(0)}312.fd-aft-tab[aria-selected="false"] .fd-aft-prog span{transform:scaleX(0)!important}313.fd-aft-pause{display:inline-flex;align-items:center;gap:8px;margin-top:20px;height:32px;padding:0 14px;border-radius:999px;font:500 13px/1 var(--fd-aft-sans);color:var(--fd-aft-mut);box-shadow:inset 0 0 0 1px var(--fd-aft-line);transition:color var(--fd-dur-1),transform var(--fd-dur-1)}314.fd-aft-pause:hover{color:var(--fd-aft-ink)}315.fd-aft-pause:active{transform:scale(.96)}316.fd-aft-pause:focus-visible{outline:2px solid rgba(181,83,46,.7);outline-offset:2px}317 318.fd-aft-stage{position:relative;display:grid;justify-items:center;padding:40px 0 0}319.fd-aft-arch{position:absolute;left:50%;top:0;bottom:40px;width:min(440px,100%);transform:translateX(-50%);border-radius:220px 220px 6px 6px;background:linear-gradient(180deg,var(--fd-aft-blush),#E4C3AD);overflow:hidden}320.fd-aft-arch::after{content:"";position:absolute;left:-20%;right:-20%;bottom:-30%;height:70%;border-radius:50%;background:radial-gradient(closest-side,rgba(181,83,46,.25),transparent)}321.fd-aft-phone{position:relative;width:300px;height:620px;border-radius:48px;padding:12px;background:#1F1512;box-shadow:inset 0 0 0 1.5px #4A3A33,0 1px 0 rgba(255,255,255,.08) inset,0 50px 80px -40px rgba(43,28,22,.55),0 20px 40px -20px rgba(43,28,22,.35)}322.fd-aft-island{position:absolute;left:50%;top:22px;width:92px;height:26px;border-radius:999px;background:#000;transform:translateX(-50%);z-index:3}323.fd-aft-status{position:absolute;left:12px;right:12px;top:12px;height:48px;padding:0 30px;display:flex;align-items:center;justify-content:space-between;z-index:2;font:600 14px/1 var(--fd-aft-sans);color:var(--fd-aft-ink)}324.fd-aft-sig{display:flex;align-items:flex-end;gap:2px;height:11px}325.fd-aft-sig i{display:block;width:3px;border-radius:1px;background:var(--fd-aft-ink)}326.fd-aft-sig i:nth-child(1){height:4px}.fd-aft-sig i:nth-child(2){height:6px}.fd-aft-sig i:nth-child(3){height:8px}.fd-aft-sig i:nth-child(4){height:11px}327.fd-aft-panel{position:relative;width:100%;height:100%;border-radius:36px;overflow:hidden;background:var(--fd-aft-cream)}328.fd-aft-home{position:absolute;left:50%;bottom:20px;width:110px;height:4px;border-radius:2px;background:var(--fd-aft-ink);opacity:.8;transform:translateX(-50%)}329.fd-aft-screen{position:absolute;inset:0;padding:60px 18px 30px;opacity:0;transform:translateY(14px);transition:opacity var(--fd-dur-3) var(--fd-ease-in-out),transform var(--fd-dur-4) var(--fd-ease-out);pointer-events:none}330.fd-aft-screen[data-on]{opacity:1;transform:none;pointer-events:auto}331.fd-aft-caption{margin-top:20px;font:italic 400 15px/20px var(--fd-aft-serif);color:var(--fd-aft-mut)}332 333/* screen content */334.fd-aft-scr{display:flex;flex-direction:column;height:100%;font:400 13px/1.4 var(--fd-aft-sans);color:var(--fd-aft-ink)}335.fd-aft-s-k{font:600 11px/14px var(--fd-aft-sans);letter-spacing:.12em;text-transform:uppercase;color:var(--fd-aft-clay)}336.fd-aft-s-h{margin-top:4px;font:400 26px/1.1 var(--fd-aft-serif);letter-spacing:-.015em}337.fd-aft-s-l{margin:14px 0 8px;font:600 12px/16px var(--fd-aft-sans);color:var(--fd-aft-mut)}338.fd-aft-days{display:grid;grid-template-columns:repeat(5,1fr);gap:6px;margin-top:16px}339.fd-aft-days span{display:flex;flex-direction:column;align-items:center;gap:2px;padding:8px 0;border-radius:14px;background:#F1E7DB;font:600 17px/1 var(--fd-aft-sans)}340.fd-aft-days small{font:500 10px/1 var(--fd-aft-sans);letter-spacing:.06em;text-transform:uppercase;color:var(--fd-aft-mut)}341.fd-aft-days span[data-on]{background:var(--fd-aft-ink);color:var(--fd-aft-cream)}342.fd-aft-days span[data-on] small{color:#CDB8AA}343.fd-aft-slots{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}344.fd-aft-slots span{display:grid;place-items:center;height:34px;border-radius:10px;box-shadow:inset 0 0 0 1px var(--fd-aft-line);font:500 13px/1 var(--fd-aft-sans);font-variant-numeric:tabular-nums}345.fd-aft-slots span[data-gone]{color:#BCA99C;text-decoration:line-through;box-shadow:none;background:#F3ECE3}346.fd-aft-slots span[data-on]{background:var(--fd-aft-clay);color:#fff;box-shadow:none}347.fd-aft-doc{display:flex;align-items:center;gap:10px;margin-top:16px;padding-top:14px;border-top:1px solid var(--fd-aft-line)}348.fd-aft-doc img{width:40px;height:40px;border-radius:50%;object-fit:cover}349.fd-aft-doc span{display:flex;flex-direction:column}350.fd-aft-doc b{font-weight:600;font-size:14px}351.fd-aft-doc small{font-size:12px;color:var(--fd-aft-mut)}352.fd-aft-pill{margin-top:12px;font:500 12px/16px var(--fd-aft-sans);color:var(--fd-aft-sage)}353.fd-aft-cta{margin-top:auto;display:grid;place-items:center;height:46px;border-radius:999px;background:var(--fd-aft-ink);color:var(--fd-aft-cream);font:600 15px/1 var(--fd-aft-sans)}354.fd-aft-res{margin-top:18px;display:flex;flex-direction:column;gap:16px}355.fd-aft-res-r{padding:14px;border-radius:16px;background:#fff;box-shadow:0 0 0 1px var(--fd-aft-line)}356.fd-aft-res-t{display:flex;align-items:baseline;gap:8px;font:500 13px/18px var(--fd-aft-sans)}357.fd-aft-res-t em{font-style:normal;font:600 10px/1 var(--fd-aft-sans);letter-spacing:.06em;text-transform:uppercase;padding:3px 6px;border-radius:999px;background:#F6DCCF;color:#8C3517}358.fd-aft-res-t b{margin-left:auto;font:400 22px/1 var(--fd-aft-serif)}359.fd-aft-res-t b small{font:400 11px/1 var(--fd-aft-sans);color:var(--fd-aft-mut)}360.fd-aft-range{position:relative;height:6px;margin-top:12px;border-radius:3px;background:#EFE5DA}361.fd-aft-ok{position:absolute;top:0;bottom:0;border-radius:3px;background:#C9D5C2}362.fd-aft-dot{position:absolute;top:50%;width:12px;height:12px;margin:-6px 0 0 -6px;border-radius:50%;background:var(--fd-aft-sage);box-shadow:0 0 0 2px #fff}363.fd-aft-dot[data-flag]{background:var(--fd-aft-clay)}364.fd-aft-note{display:flex;gap:10px;margin-top:auto;padding:12px;border-radius:16px;background:#F1E7DB}365.fd-aft-note img{width:24px;height:24px;border-radius:50%;object-fit:cover}366.fd-aft-note p{font:400 14px/1.4 var(--fd-aft-serif);font-style:italic}367.fd-aft-note small{display:block;margin-top:4px;font:500 11px/1 var(--fd-aft-sans);font-style:normal;color:var(--fd-aft-mut)}368.fd-aft-meds{margin-top:18px;display:flex;flex-direction:column}369.fd-aft-meds li{display:grid;grid-template-columns:44px minmax(0,1fr) 26px;gap:8px;align-items:center;padding:12px 0;border-bottom:1px solid var(--fd-aft-line)}370.fd-aft-med-t{font:500 12px/1 var(--fd-aft-sans);font-variant-numeric:tabular-nums;color:var(--fd-aft-mut)}371.fd-aft-med-n{display:flex;flex-direction:column}372.fd-aft-med-n b{font-weight:600;font-size:14px}373.fd-aft-med-n small{font-size:12px;color:var(--fd-aft-mut)}374.fd-aft-med-c{display:grid;place-items:center;width:26px;height:26px;border-radius:50%;box-shadow:inset 0 0 0 1.5px var(--fd-aft-line)}375.fd-aft-meds li[data-s="taken"] .fd-aft-med-c{background:var(--fd-aft-sage);color:#fff;box-shadow:none}376.fd-aft-meds li[data-s="taken"] .fd-aft-med-n b{text-decoration:line-through;text-decoration-color:rgba(43,28,22,.35)}377.fd-aft-meds li[data-s="due"]{margin:0 -10px;padding:12px 10px;border-radius:14px;background:#F6DCCF;border-color:transparent}378.fd-aft-meds li[data-s="due"] .fd-aft-med-t{color:#8C3517;font-weight:700}379.fd-aft-meds li[data-s="due"] .fd-aft-med-c{box-shadow:inset 0 0 0 1.5px var(--fd-aft-clay)}380.fd-aft-refill{margin-top:auto;padding:14px;border-radius:16px;background:#fff;box-shadow:0 0 0 1px var(--fd-aft-line)}381.fd-aft-refill-bar{height:6px;border-radius:3px;background:#EFE5DA}382.fd-aft-refill-bar i{display:block;width:21%;height:100%;border-radius:3px;background:var(--fd-aft-clay)}383.fd-aft-refill p{margin-top:10px;font-size:12px;line-height:1.45;color:var(--fd-aft-mut)}384.fd-aft-refill b{color:var(--fd-aft-ink);font-weight:600}385.fd-aft-chat{padding-top:0}386.fd-aft-chat-h{display:flex;align-items:center;gap:10px;padding-bottom:12px;border-bottom:1px solid var(--fd-aft-line)}387.fd-aft-chat-h img{width:32px;height:32px;border-radius:50%;object-fit:cover}388.fd-aft-chat-h span{display:flex;flex-direction:column}389.fd-aft-chat-h b{font-size:14px;font-weight:600}390.fd-aft-chat-h small{font-size:11px;color:var(--fd-aft-sage)}391.fd-aft-msgs{display:flex;flex-direction:column;gap:6px;padding-top:14px}392.fd-aft-msgs>*{opacity:0;transform:translateY(8px);transition:opacity var(--fd-dur-3) var(--fd-ease-out),transform var(--fd-dur-3) var(--fd-ease-out)}393[data-on] .fd-aft-msgs>*{opacity:1;transform:none;transition-delay:calc(300ms + var(--i) * 420ms)}394.fd-aft-me,.fd-aft-them{max-width:84%;padding:9px 12px;border-radius:16px;font-size:13px;line-height:1.4}395.fd-aft-me{align-self:flex-end;background:var(--fd-aft-ink);color:var(--fd-aft-cream);border-bottom-right-radius:4px}396.fd-aft-them{align-self:flex-start;background:#fff;box-shadow:0 0 0 1px var(--fd-aft-line);border-bottom-left-radius:4px}397.fd-aft-photo{align-self:flex-end;width:120px;height:78px;border-radius:12px;background:radial-gradient(40% 50% at 55% 50%,#D99A86,transparent),radial-gradient(30% 40% at 35% 60%,#E0A792,transparent),linear-gradient(135deg,#E9C4AE,#D8AE95)}398.fd-aft-typing{align-self:flex-start;display:flex;gap:4px;padding:10px 12px;border-radius:16px;background:#fff;box-shadow:0 0 0 1px var(--fd-aft-line)}399.fd-aft-typing i{display:block;width:6px;height:6px;border-radius:50%;background:var(--fd-aft-mut);animation:fd-aft-bounce 1.2s var(--fd-ease-in-out) infinite}400.fd-aft-typing i:nth-child(2){animation-delay:.15s}.fd-aft-typing i:nth-child(3){animation-delay:.3s}401@keyframes fd-aft-bounce{0%,60%,100%{transform:none;opacity:.5}30%{transform:translateY(-3px);opacity:1}}402.fd-aft-compose{margin-top:auto;height:40px;display:flex;align-items:center;padding:0 14px;border-radius:999px;background:#fff;box-shadow:0 0 0 1px var(--fd-aft-line);color:#B19C8E;font-size:13px}403 404@media (max-width:900px){405 .fd-aft-wrap{grid-template-columns:minmax(0,1fr);gap:0}406 .fd-aft-copy{display:contents}407 .fd-aft-stage{order:1;margin-top:36px}408 .fd-aft-tabs{order:2;margin-top:28px}409 .fd-aft-pause{order:3;justify-self:start}410}411@media (max-width:420px){412 .fd-aft-phone{width:280px;height:580px}413 .fd-aft-tab-l{font-size:20px}414}415@media (prefers-reduced-motion:reduce){416 .fd-aft *,.fd-aft-msgs>*{transition-duration:1ms!important;transition-delay:0ms!important;animation:none!important}417 .fd-aft-screen{transform:none}418}419@media (prefers-contrast:more){.fd-aft-phone{box-shadow:0 0 0 2px currentColor}}420`;421 422export function Demo() {423 return (424 <div className="fd-aftd">425 <style>{`.fd-aftd{background:#F4ECE1;min-height:100vh}.fd-aftd-nav{box-sizing:border-box;max-width:1272px;margin:0 auto;height:64px;padding:0 clamp(16px,4vw,56px);display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid rgba(43,28,22,.12);font:400 14px/20px ui-sans-serif,-apple-system,BlinkMacSystemFont,"Inter",sans-serif;color:#7A6457}.fd-aftd-nav b{font:400 24px/1 "Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;font-style:italic;letter-spacing:-.02em;color:#2B1C16}.fd-aftd-nav b i{font-style:normal;color:#B5532E}`}</style>426 <div className="fd-aftd-nav"><b>Wren<i>.</i></b><span>Hyde Park, Leeds · 0113 496 0021</span></div>427 <AutoplayFeatureTabs />428 </div>429 );430}More Feature Sections. Same file contract.
Questions about Autoplay Feature Tabs
Run npx shadcn@latest add https://freedesignmd.com/r/autoplay-feature-tabs.json, or open the page and copy the file into your project. It has zero dependencies besides React. What you copy is the exact file that renders the live demo.
Autoplay feature tabs for React: tabs advance on a filling progress bar, pause on hover, and show a phone screen built in code. It is a free, MIT-licensed React feature sections component from the freedesignmd library.
Yes. It is free and MIT-licensed, use, modify and ship it in personal and commercial projects without attribution.
All evergreen browsers. Description reveal uses grid-template-rows interpolation (Chrome 107+, Safari 16+, Firefox 66+).
Yes. It is a plain React component with its own styles, so it runs in Next.js, Vite, Remix and Lovable projects, and it needs no Tailwind setup. It has zero dependencies besides React.


