Open demo

CSS code

HTML
<div><span class="lbl">Route line that draws itself, with stages</span>
<section class="rt-sec" aria-labelledby="rt-title">
  <p class="rt-kicker">Fishermen's Trail, Alentejo</p>
  <h2 id="rt-title">Three days on the cliffs</h2>
  <div class="rt-grid">
    <figure class="rt-map">
    <svg viewBox="0 0 600 400" role="img" aria-label="Illustrated map of a 57 km coastal trail from Porto Covo south to Zambujeira do Mar, with overnight stops in Vila Nova de Milfontes and Almograve.">
      <rect width="600" height="400" fill="#a9d3e0"/>
      <path d="M302 0C318 40 292 70 300 110C306 140 270 150 276 172C284 210 300 220 288 252C280 276 292 300 276 330C266 352 270 376 258 400H600V0Z" fill="#efe7d3"/>
      <path d="M302 0C318 40 292 70 300 110C306 140 270 150 276 172C284 210 300 220 288 252C280 276 292 300 276 330C266 352 270 376 258 400" fill="none" stroke="#f9f5ea" stroke-width="6"/>
      <path d="M296 166C340 172 380 150 430 160C470 168 520 150 600 158" fill="none" stroke="#a9d3e0" stroke-width="9" stroke-linecap="round"/>
      <g fill="#dfe8c8"><ellipse cx="450" cy="80" rx="90" ry="40"/><ellipse cx="470" cy="270" rx="100" ry="54"/><ellipse cx="400" cy="360" rx="70" ry="30"/></g>
      <g fill="none" stroke="#c9d6ad" stroke-width="1.5"><ellipse cx="450" cy="80" rx="60" ry="24"/><ellipse cx="470" cy="270" rx="66" ry="34"/><ellipse cx="470" cy="270" rx="34" ry="16"/></g>
      <g fill="#55604f" font-family="inherit" font-size="11" font-weight="700" letter-spacing="1.5"><text x="96" y="210">ATLANTIC OCEAN</text><text x="500" y="140">RIO MIRA</text></g>
      <g transform="translate(548 40)" fill="#1f2a1f"><path d="M0-18L6 4 0 0-6 4Z"/><text y="18" font-size="11" font-weight="800" text-anchor="middle" font-family="inherit">N</text></g>
      <g transform="translate(40 378)" fill="#1f2a1f" font-family="inherit" font-size="10" font-weight="700"><rect width="80" height="5" fill="none" stroke="#1f2a1f"/><rect width="40" height="5"/><text y="-6">5 km</text></g>
      <path d="M332 34C352 70 318 96 326 128C332 150 296 150 300 164C306 196 330 214 314 246C302 270 318 300 300 330C290 348 296 360 282 372" fill="none" stroke="#7a5c3b" stroke-width="3" stroke-dasharray="2 6" stroke-linecap="round" opacity=".55"/>
      <path class="rt-line" d="M332 34C352 70 318 96 326 128C332 150 296 150 300 164C306 196 330 214 314 246C302 270 318 300 300 330C290 348 296 360 282 372" pathLength="1"/>
      <g class="rt-stop"><circle cx="332" cy="34" r="9"/><text x="348" y="38">Porto Covo</text></g>
      <g class="rt-stop"><circle cx="300" cy="164" r="7"/><text x="316" y="168">Vila Nova de Milfontes</text></g>
      <g class="rt-stop"><circle cx="314" cy="246" r="7"/><text x="330" y="250">Almograve</text></g>
      <g class="rt-stop"><circle cx="282" cy="372" r="9"/><text x="298" y="376">Zambujeira do Mar</text></g>
    </svg>
    </figure>
    <div>
      <dl class="rt-stats"><div><dt>Distance</dt><dd>57 km</dd></div><div><dt>Days</dt><dd>3</dd></div><div><dt>Ascent</dt><dd>610 m</dd></div></dl>
      <ol class="rt-legs">
        <li><strong>Porto Covo to Vila Nova de Milfontes</strong><span>20 km · 6 h</span><p>Sandy paths along the cliffs, then the Mira estuary.</p></li>
        <li><strong>Vila Nova de Milfontes to Almograve</strong><span>15 km · 5 h</span><p>Ferry or bridge over the river, then dunes.</p></li>
        <li><strong>Almograve to Zambujeira do Mar</strong><span>22 km · 7 h</span><p>Stork nests on sea stacks and the Cavaleiro lighthouse.</p></li>
      </ol>
    </div>
  </div>
</section></div>
<div><span class="lbl">Itinerary that highlights its leg on the map (CSS :has)</span>
<section class="rt-sec it" aria-labelledby="it-title">
  <p class="rt-kicker">Douro valley by train and boat</p>
  <h2 id="it-title">Four days upriver</h2>
  <div class="rt-grid">
    <figure class="it-map">
    <svg viewBox="0 0 600 300" role="img" aria-label="Map of the Douro valley route from Porto east to Pocinho in four day legs.">
      <rect width="600" height="300" fill="#efe9da"/>
      <g fill="#dbe5c3"><path d="M120 0C180 60 260 70 330 40S480 0 520 0Z"/><path d="M150 300C220 230 330 240 420 260S560 300 600 290V300Z"/></g>
      <path d="M0 196C90 170 160 214 250 166S330 214 380 196S430 156 470 162S540 190 600 172" fill="none" stroke="#9cc7da" stroke-width="12" stroke-linecap="round"/>
      <g fill="#55604f" font-family="inherit" font-size="11" font-weight="700" letter-spacing="1.5"><text x="120" y="226">RIO DOURO</text></g>
      <path class="it-seg" data-day="1" d="M40 172C110 150 170 186 248 146"/>
      <path class="it-seg" data-day="2" d="M248 146C300 120 320 190 372 178"/>
      <path class="it-seg" data-day="3" d="M372 178C410 170 420 140 462 146"/>
      <path class="it-seg" data-day="4" d="M462 146C500 150 520 180 566 160"/>
      <g class="it-town"><circle cx="40" cy="172" r="7"/><text x="40" y="156" text-anchor="start">Porto</text></g>
      <g class="it-town"><circle cx="248" cy="146" r="7"/><text x="248" y="130" text-anchor="middle">Peso da Régua</text></g>
      <g class="it-town"><circle cx="372" cy="178" r="7"/><text x="372" y="162" text-anchor="middle">Pinhão</text></g>
      <g class="it-town"><circle cx="462" cy="146" r="7"/><text x="462" y="130" text-anchor="middle">Tua</text></g>
      <g class="it-town"><circle cx="566" cy="160" r="7"/><text x="566" y="144" text-anchor="end">Pocinho</text></g>
    </svg>
    </figure>
    <fieldset>
      <legend>Show a day on the map</legend>
      <label class="it-day"><input type="radio" name="it-day" id="it-d1" checked><b><span>Day 1</span> · Porto to Peso da Régua</b><small>Train along the river, 2 h. Evening at a quinta above the town.</small></label>
      <label class="it-day"><input type="radio" name="it-day" id="it-d2"><b><span>Day 2</span> · Peso da Régua to Pinhão</b><small>Rabelo boat cruise, 1 h 30 min, with a stop at the Bagaúste lock.</small></label>
      <label class="it-day"><input type="radio" name="it-day" id="it-d3"><b><span>Day 3</span> · Pinhão to Tua</b><small>Short train hop, then a 9 km walk back through the terraces.</small></label>
      <label class="it-day"><input type="radio" name="it-day" id="it-d4"><b><span>Day 4</span> · Tua to Pocinho</b><small>End of the line: almond groves and the Côa valley rock art.</small></label>
    </fieldset>
  </div>
</section></div>
<div><span class="lbl">Floor plan with a way-finding line (CSS :has)</span>
<section class="rt-sec fp" aria-labelledby="fp-title">
  <p class="rt-kicker" style="color:#0f766e">Ground floor</p>
  <h2 id="fp-title">Find your room</h2>
  <fieldset style="margin-top:14px">
    <legend>Show the way to</legend>
    <div class="fp-opts" style="display:flex;gap:8px">
      <label class="fp-opt"><input type="radio" name="fp-room" id="fp-hall">Main hall</label>
      <label class="fp-opt"><input type="radio" name="fp-room" id="fp-wa" checked>Workshop A</label>
      <label class="fp-opt"><input type="radio" name="fp-room" id="fp-wb">Workshop B</label>
      <label class="fp-opt"><input type="radio" name="fp-room" id="fp-cafe">Café</label>
      <label class="fp-opt"><input type="radio" name="fp-room" id="fp-cloak">Cloakroom</label>
    </div>
  </fieldset>
  <figure class="fp-map" style="margin-top:16px">
    <svg viewBox="0 0 600 360" role="img" aria-label="Floor plan of the ground floor with the main hall, two workshop rooms, a café, a cloakroom and toilets. The entrance is at the bottom right.">
      <rect x="20" y="20" width="560" height="320" rx="6" fill="#fbfaf5" stroke="#1f2a1f" stroke-width="4"/>
      <g class="fp-room hall"><rect x="40" y="40" width="300" height="170" rx="4"/><text x="190" y="129">Main hall</text></g>
      <g class="fp-room wa"><rect x="360" y="40" width="200" height="100" rx="4"/><text x="460" y="94">Workshop A</text></g>
      <g class="fp-room wb"><rect x="360" y="160" width="200" height="100" rx="4"/><text x="460" y="214">Workshop B</text></g>
      <g class="fp-room cafe"><rect x="40" y="230" width="180" height="110" rx="4"/><text x="130" y="289">Café</text></g>
      <g class="fp-room cloak"><rect x="240" y="280" width="100" height="60" rx="4"/><text x="290" y="314">Cloakroom</text></g>
      <g class="fp-static"><rect x="360" y="280" width="90" height="60" rx="4"/><text x="405" y="314">Toilets</text></g>
      <path d="M470 340h70" stroke="#fbfaf5" stroke-width="6"/>
      <text x="505" y="356" class="fp-door" text-anchor="middle">Entrance</text>
      <path class="fp-way" data-way="hall" d="M505 322V270H350V220H190V210"/>
      <path class="fp-way" data-way="wa" d="M505 322V270H350V90H360"/>
      <path class="fp-way" data-way="wb" d="M505 322V270H460V260"/>
      <path class="fp-way" data-way="cafe" d="M505 322V270H230V292H220"/>
      <path class="fp-way" data-way="cloak" d="M505 322V270H290V280"/>
      <circle class="fp-you" cx="505" cy="322" r="8"/>
    </svg>
  </figure>
  <p class="fp-note">The dotted line starts at the entrance, marked with a green dot.</p>
</section></div>
CSS
/* Illustrated maps (not to scale): a route drawn with stroke-dashoffset, an itinerary that highlights its leg, and a floor plan. */
.rt-map svg,.it-map svg,.fp-map svg{display:block;width:100%;height:auto;border-radius:16px}
.rt-line{fill:none;stroke:#c2410c;stroke-width:4.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:1;stroke-dashoffset:1;animation:rt-draw 3.2s ease-in-out .3s forwards}
@keyframes rt-draw{to{stroke-dashoffset:0}}
.rt-stop circle{fill:#fff;stroke:#c2410c;stroke-width:4}
.rt-stop text{font-family:inherit;font-size:15px;font-weight:800;fill:#1f2a1f;paint-order:stroke;stroke:#efe7d3;stroke-width:4px;stroke-linejoin:round}
.it-seg{fill:none;stroke:#8a9a78;stroke-width:4;stroke-linecap:round;stroke-dasharray:7 7;transition:stroke .2s,stroke-width .2s}
.it-town circle{fill:#fff;stroke:#1f2a1f;stroke-width:3}
.it-town text{font-family:inherit;font-size:15px;font-weight:800;fill:#1f2a1f;paint-order:stroke;stroke:#efe9da;stroke-width:4px}
.fp-room rect{fill:#eef0e6;stroke:#1f2a1f;stroke-width:2;transition:fill .2s}
.fp-room text,.fp-static text{font-family:inherit;font-size:15px;font-weight:700;fill:#1f2a1f;text-anchor:middle}
.fp-static rect{fill:#e4e2d8;stroke:#1f2a1f;stroke-width:2}
.fp-door{font-family:inherit;font-size:12px;font-weight:700;fill:#55604f}
.fp-map svg{max-width:760px;margin-inline:auto}
.fp-way{fill:none;stroke:#0f766e;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:2 8;opacity:0;transition:opacity .2s}
.fp-you{fill:#0f766e;stroke:#fff;stroke-width:3}
@media (prefers-reduced-motion:reduce){.rt-line{animation:none;stroke-dashoffset:0}.it-seg,.fp-room rect,.fp-way{transition:none}}
.it:has(#it-d1:checked) .it-seg[data-day="1"],.it:has(#it-d2:checked) .it-seg[data-day="2"],.it:has(#it-d3:checked) .it-seg[data-day="3"],.it:has(#it-d4:checked) .it-seg[data-day="4"]{stroke:#c2410c;stroke-width:6;stroke-dasharray:none}
.fp:has(#fp-hall:checked) .fp-room.hall rect,.fp:has(#fp-wa:checked) .fp-room.wa rect,.fp:has(#fp-wb:checked) .fp-room.wb rect,.fp:has(#fp-cafe:checked) .fp-room.cafe rect,.fp:has(#fp-cloak:checked) .fp-room.cloak rect{fill:#99f6e4}
.fp:has(#fp-hall:checked) [data-way="hall"],.fp:has(#fp-wa:checked) [data-way="wa"],.fp:has(#fp-wb:checked) [data-way="wb"],.fp:has(#fp-cafe:checked) [data-way="cafe"],.fp:has(#fp-cloak:checked) [data-way="cloak"]{opacity:1}
.rt-sec{--ink:#1f2a1f;--muted:#55604f;--line:#e1dccd;background:#fff;border:1px solid var(--line);border-radius:22px;padding:24px;color:var(--ink)}
.rt-sec h2{font-size:1.35rem;letter-spacing:-.02em}
.rt-kicker{font-size:.75rem;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:#9a3412}
.rt-grid{display:grid;gap:22px;margin-top:16px}
@media (min-width:880px){.rt-grid{grid-template-columns:minmax(0,1.25fr) minmax(0,1fr);align-items:start}}
.rt-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:14px}
.rt-stats div{padding:10px 12px;border-radius:12px;background:#f6f3ea}
.rt-stats dt{font-size:.72rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.rt-stats dd{font-size:1.15rem;font-weight:800}
.rt-legs{list-style:none;display:grid;gap:14px;margin-top:18px;counter-reset:leg}
.rt-legs li{position:relative;padding-left:40px;counter-increment:leg}
.rt-legs li::before{content:counter(leg);position:absolute;left:0;top:0;width:28px;height:28px;border-radius:50%;background:#c2410c;color:#fff;display:grid;place-items:center;font-size:.8rem;font-weight:800}
.rt-legs strong{display:block;font-size:.95rem}
.rt-legs span{font-size:.8rem;font-weight:700;color:#9a3412}
.rt-legs p{font-size:.88rem;color:var(--muted)}
.it fieldset,.fp fieldset{border:0;display:grid;gap:8px}
.it legend,.fp legend{font-size:.8rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-bottom:8px}
.it-day{display:grid;grid-template-columns:auto 1fr;gap:2px 12px;padding:12px 14px;border:1px solid var(--line);border-radius:14px;cursor:pointer;transition:border-color .15s,background-color .15s}
.it-day input,.fp-opt input{grid-row:span 2;width:18px;height:18px;margin-top:3px;accent-color:#c2410c}
.it-day input:focus-visible,.fp-opt input:focus-visible{outline:3px solid #1f2a1f;outline-offset:2px}
.it-day b{font-size:.92rem}
.it-day b span{color:#9a3412}
.it-day small{font-size:.83rem;color:var(--muted)}
.it-day:has(input:checked){border-color:#c2410c;background:#fff7ed}
.fp-opts{display:flex!important;flex-wrap:wrap}
.fp-opt{display:inline-flex;align-items:center;gap:8px;padding:8px 14px 8px 10px;border:1px solid var(--line);border-radius:999px;font-size:.9rem;font-weight:600;cursor:pointer}
.fp-opt input{margin:0;accent-color:#0f766e}
.fp-opt:has(input:checked){border-color:#0f766e;background:#f0fdfa}
.fp-note{margin-top:10px;font-size:.82rem;color:var(--muted)}
@media (max-width:640px){.rt-sec{padding:16px}}
@media (prefers-reduced-motion:reduce){.it-day{transition:none}}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Route line that draws itself, with stages</span>
<section class="rt-sec card rounded-4 p-3 p-sm-4" aria-labelledby="rt-title">
  <p class="rt-kicker fw-bolder text-uppercase mb-0">Fishermen's Trail, Alentejo</p>
  <h2 id="rt-title" class="h4 fw-bold mt-1 mb-0">Three days on the cliffs</h2>
  <div class="row g-4 mt-0">
    <figure class="rt-map col-lg-7 mb-0">
    <svg viewBox="0 0 600 400" role="img" aria-label="Illustrated map of a 57 km coastal trail from Porto Covo south to Zambujeira do Mar, with overnight stops in Vila Nova de Milfontes and Almograve.">
      <rect width="600" height="400" fill="#a9d3e0"/>
      <path d="M302 0C318 40 292 70 300 110C306 140 270 150 276 172C284 210 300 220 288 252C280 276 292 300 276 330C266 352 270 376 258 400H600V0Z" fill="#efe7d3"/>
      <path d="M302 0C318 40 292 70 300 110C306 140 270 150 276 172C284 210 300 220 288 252C280 276 292 300 276 330C266 352 270 376 258 400" fill="none" stroke="#f9f5ea" stroke-width="6"/>
      <path d="M296 166C340 172 380 150 430 160C470 168 520 150 600 158" fill="none" stroke="#a9d3e0" stroke-width="9" stroke-linecap="round"/>
      <g fill="#dfe8c8"><ellipse cx="450" cy="80" rx="90" ry="40"/><ellipse cx="470" cy="270" rx="100" ry="54"/><ellipse cx="400" cy="360" rx="70" ry="30"/></g>
      <g fill="none" stroke="#c9d6ad" stroke-width="1.5"><ellipse cx="450" cy="80" rx="60" ry="24"/><ellipse cx="470" cy="270" rx="66" ry="34"/><ellipse cx="470" cy="270" rx="34" ry="16"/></g>
      <g fill="#55604f" font-family="inherit" font-size="11" font-weight="700" letter-spacing="1.5"><text x="96" y="210">ATLANTIC OCEAN</text><text x="500" y="140">RIO MIRA</text></g>
      <g transform="translate(548 40)" fill="#1f2a1f"><path d="M0-18L6 4 0 0-6 4Z"/><text y="18" font-size="11" font-weight="800" text-anchor="middle" font-family="inherit">N</text></g>
      <g transform="translate(40 378)" fill="#1f2a1f" font-family="inherit" font-size="10" font-weight="700"><rect width="80" height="5" fill="none" stroke="#1f2a1f"/><rect width="40" height="5"/><text y="-6">5 km</text></g>
      <path d="M332 34C352 70 318 96 326 128C332 150 296 150 300 164C306 196 330 214 314 246C302 270 318 300 300 330C290 348 296 360 282 372" fill="none" stroke="#7a5c3b" stroke-width="3" stroke-dasharray="2 6" stroke-linecap="round" opacity=".55"/>
      <path class="rt-line" d="M332 34C352 70 318 96 326 128C332 150 296 150 300 164C306 196 330 214 314 246C302 270 318 300 300 330C290 348 296 360 282 372" pathLength="1"/>
      <g class="rt-stop"><circle cx="332" cy="34" r="9"/><text x="348" y="38">Porto Covo</text></g>
      <g class="rt-stop"><circle cx="300" cy="164" r="7"/><text x="316" y="168">Vila Nova de Milfontes</text></g>
      <g class="rt-stop"><circle cx="314" cy="246" r="7"/><text x="330" y="250">Almograve</text></g>
      <g class="rt-stop"><circle cx="282" cy="372" r="9"/><text x="298" y="376">Zambujeira do Mar</text></g>
    </svg>
    </figure>
    <div class="col-lg-5">
      <dl class="rt-stats d-grid gap-2 mb-0" style="grid-template-columns:repeat(3,1fr)"><div class="rounded-3" style="padding:10px 12px"><dt class="fw-bold text-uppercase">Distance</dt><dd class="fw-bolder mb-0">57 km</dd></div><div class="rounded-3" style="padding:10px 12px"><dt class="fw-bold text-uppercase">Days</dt><dd class="fw-bolder mb-0">3</dd></div><div class="rounded-3" style="padding:10px 12px"><dt class="fw-bold text-uppercase">Ascent</dt><dd class="fw-bolder mb-0">610 m</dd></div></dl>
      <ol class="rt-legs list-unstyled d-grid gap-3 mt-3 mb-0">
        <li class="d-flex gap-3"><span class="rt-num rounded-circle d-grid flex-shrink-0 text-white fw-bolder" style="place-items:center" aria-hidden="true">1</span><div><strong class="d-block" style="font-size:.95rem">Porto Covo to Vila Nova de Milfontes</strong><span class="leg fw-bold">20 km · 6 h</span><p class="muted mb-0" style="font-size:.88rem">Sandy paths along the cliffs, then the Mira estuary.</p></div></li>
        <li class="d-flex gap-3"><span class="rt-num rounded-circle d-grid flex-shrink-0 text-white fw-bolder" style="place-items:center" aria-hidden="true">2</span><div><strong class="d-block" style="font-size:.95rem">Vila Nova de Milfontes to Almograve</strong><span class="leg fw-bold">15 km · 5 h</span><p class="muted mb-0" style="font-size:.88rem">Ferry or bridge over the river, then dunes.</p></div></li>
        <li class="d-flex gap-3"><span class="rt-num rounded-circle d-grid flex-shrink-0 text-white fw-bolder" style="place-items:center" aria-hidden="true">3</span><div><strong class="d-block" style="font-size:.95rem">Almograve to Zambujeira do Mar</strong><span class="leg fw-bold">22 km · 7 h</span><p class="muted mb-0" style="font-size:.88rem">Stork nests on sea stacks and the Cavaleiro lighthouse.</p></div></li>
      </ol>
    </div>
  </div>
</section></div>
<div><span class="lbl">Itinerary that highlights its leg on the map (CSS :has)</span>
<section class="rt-sec it card rounded-4 p-3 p-sm-4" aria-labelledby="it-title">
  <p class="rt-kicker fw-bolder text-uppercase mb-0">Douro valley by train and boat</p>
  <h2 id="it-title" class="h4 fw-bold mt-1 mb-0">Four days upriver</h2>
  <div class="row g-4 mt-0">
    <figure class="it-map col-lg-7 mb-0">
    <svg viewBox="0 0 600 300" role="img" aria-label="Map of the Douro valley route from Porto east to Pocinho in four day legs.">
      <rect width="600" height="300" fill="#efe9da"/>
      <g fill="#dbe5c3"><path d="M120 0C180 60 260 70 330 40S480 0 520 0Z"/><path d="M150 300C220 230 330 240 420 260S560 300 600 290V300Z"/></g>
      <path d="M0 196C90 170 160 214 250 166S330 214 380 196S430 156 470 162S540 190 600 172" fill="none" stroke="#9cc7da" stroke-width="12" stroke-linecap="round"/>
      <g fill="#55604f" font-family="inherit" font-size="11" font-weight="700" letter-spacing="1.5"><text x="120" y="226">RIO DOURO</text></g>
      <path class="it-seg" data-day="1" d="M40 172C110 150 170 186 248 146"/>
      <path class="it-seg" data-day="2" d="M248 146C300 120 320 190 372 178"/>
      <path class="it-seg" data-day="3" d="M372 178C410 170 420 140 462 146"/>
      <path class="it-seg" data-day="4" d="M462 146C500 150 520 180 566 160"/>
      <g class="it-town"><circle cx="40" cy="172" r="7"/><text x="40" y="156" text-anchor="start">Porto</text></g>
      <g class="it-town"><circle cx="248" cy="146" r="7"/><text x="248" y="130" text-anchor="middle">Peso da Régua</text></g>
      <g class="it-town"><circle cx="372" cy="178" r="7"/><text x="372" y="162" text-anchor="middle">Pinhão</text></g>
      <g class="it-town"><circle cx="462" cy="146" r="7"/><text x="462" y="130" text-anchor="middle">Tua</text></g>
      <g class="it-town"><circle cx="566" cy="160" r="7"/><text x="566" y="144" text-anchor="end">Pocinho</text></g>
    </svg>
    </figure>
    <fieldset class="col-lg-5 d-grid gap-2">
      <legend class="small fw-bolder text-uppercase muted mb-2" style="letter-spacing:.08em">Show a day on the map</legend>
      <label class="it-day form-check d-grid border rounded-4 px-3 py-2 m-0"><input class="form-check-input float-none m-0 mt-1" style="grid-row:span 2" type="radio" name="it-day" id="it-d1" checked><b style="font-size:.92rem"><span style="color:#9a3412">Day 1</span> · Porto to Peso da Régua</b><small class="muted">Train along the river, 2 h. Evening at a quinta above the town.</small></label>
      <label class="it-day form-check d-grid border rounded-4 px-3 py-2 m-0"><input class="form-check-input float-none m-0 mt-1" style="grid-row:span 2" type="radio" name="it-day" id="it-d2"><b style="font-size:.92rem"><span style="color:#9a3412">Day 2</span> · Peso da Régua to Pinhão</b><small class="muted">Rabelo boat cruise, 1 h 30 min, with a stop at the Bagaúste lock.</small></label>
      <label class="it-day form-check d-grid border rounded-4 px-3 py-2 m-0"><input class="form-check-input float-none m-0 mt-1" style="grid-row:span 2" type="radio" name="it-day" id="it-d3"><b style="font-size:.92rem"><span style="color:#9a3412">Day 3</span> · Pinhão to Tua</b><small class="muted">Short train hop, then a 9 km walk back through the terraces.</small></label>
      <label class="it-day form-check d-grid border rounded-4 px-3 py-2 m-0"><input class="form-check-input float-none m-0 mt-1" style="grid-row:span 2" type="radio" name="it-day" id="it-d4"><b style="font-size:.92rem"><span style="color:#9a3412">Day 4</span> · Tua to Pocinho</b><small class="muted">End of the line: almond groves and the Côa valley rock art.</small></label>
    </fieldset>
  </div>
</section></div>
<div><span class="lbl">Floor plan with a way-finding line (CSS :has)</span>
<section class="rt-sec fp card rounded-4 p-3 p-sm-4" aria-labelledby="fp-title">
  <p class="rt-kicker fw-bolder text-uppercase mb-0" style="color:#0f766e">Ground floor</p>
  <h2 id="fp-title" class="h4 fw-bold mt-1 mb-0">Find your room</h2>
  <fieldset class="mt-3">
    <legend class="small fw-bolder text-uppercase muted mb-2" style="letter-spacing:.08em">Show the way to</legend>
    <div class="d-flex flex-wrap gap-2">
      <label class="fp-opt form-check d-inline-flex align-items-center gap-2 border rounded-pill ps-2 pe-3 py-2 m-0 fw-semibold" style="font-size:.9rem"><input class="form-check-input float-none m-0" type="radio" name="fp-room" id="fp-hall">Main hall</label>
      <label class="fp-opt form-check d-inline-flex align-items-center gap-2 border rounded-pill ps-2 pe-3 py-2 m-0 fw-semibold" style="font-size:.9rem"><input class="form-check-input float-none m-0" type="radio" name="fp-room" id="fp-wa" checked>Workshop A</label>
      <label class="fp-opt form-check d-inline-flex align-items-center gap-2 border rounded-pill ps-2 pe-3 py-2 m-0 fw-semibold" style="font-size:.9rem"><input class="form-check-input float-none m-0" type="radio" name="fp-room" id="fp-wb">Workshop B</label>
      <label class="fp-opt form-check d-inline-flex align-items-center gap-2 border rounded-pill ps-2 pe-3 py-2 m-0 fw-semibold" style="font-size:.9rem"><input class="form-check-input float-none m-0" type="radio" name="fp-room" id="fp-cafe">Café</label>
      <label class="fp-opt form-check d-inline-flex align-items-center gap-2 border rounded-pill ps-2 pe-3 py-2 m-0 fw-semibold" style="font-size:.9rem"><input class="form-check-input float-none m-0" type="radio" name="fp-room" id="fp-cloak">Cloakroom</label>
    </div>
  </fieldset>
  <figure class="fp-map mt-3 mb-0">
    <svg viewBox="0 0 600 360" role="img" aria-label="Floor plan of the ground floor with the main hall, two workshop rooms, a café, a cloakroom and toilets. The entrance is at the bottom right.">
      <rect x="20" y="20" width="560" height="320" rx="6" fill="#fbfaf5" stroke="#1f2a1f" stroke-width="4"/>
      <g class="fp-room hall"><rect x="40" y="40" width="300" height="170" rx="4"/><text x="190" y="129">Main hall</text></g>
      <g class="fp-room wa"><rect x="360" y="40" width="200" height="100" rx="4"/><text x="460" y="94">Workshop A</text></g>
      <g class="fp-room wb"><rect x="360" y="160" width="200" height="100" rx="4"/><text x="460" y="214">Workshop B</text></g>
      <g class="fp-room cafe"><rect x="40" y="230" width="180" height="110" rx="4"/><text x="130" y="289">Café</text></g>
      <g class="fp-room cloak"><rect x="240" y="280" width="100" height="60" rx="4"/><text x="290" y="314">Cloakroom</text></g>
      <g class="fp-static"><rect x="360" y="280" width="90" height="60" rx="4"/><text x="405" y="314">Toilets</text></g>
      <path d="M470 340h70" stroke="#fbfaf5" stroke-width="6"/>
      <text x="505" y="356" class="fp-door" text-anchor="middle">Entrance</text>
      <path class="fp-way" data-way="hall" d="M505 322V270H350V220H190V210"/>
      <path class="fp-way" data-way="wa" d="M505 322V270H350V90H360"/>
      <path class="fp-way" data-way="wb" d="M505 322V270H460V260"/>
      <path class="fp-way" data-way="cafe" d="M505 322V270H230V292H220"/>
      <path class="fp-way" data-way="cloak" d="M505 322V270H290V280"/>
      <circle class="fp-you" cx="505" cy="322" r="8"/>
    </svg>
  </figure>
  <p class="muted small mt-2 mb-0">The dotted line starts at the entrance, marked with a green dot.</p>
</section></div>
CSS
/* Illustrated maps (not to scale): a route drawn with stroke-dashoffset, an itinerary that highlights its leg, and a floor plan. */
.rt-map svg,.it-map svg,.fp-map svg{display:block;width:100%;height:auto;border-radius:16px}
.rt-line{fill:none;stroke:#c2410c;stroke-width:4.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:1;stroke-dashoffset:1;animation:rt-draw 3.2s ease-in-out .3s forwards}
@keyframes rt-draw{to{stroke-dashoffset:0}}
.rt-stop circle{fill:#fff;stroke:#c2410c;stroke-width:4}
.rt-stop text{font-family:inherit;font-size:15px;font-weight:800;fill:#1f2a1f;paint-order:stroke;stroke:#efe7d3;stroke-width:4px;stroke-linejoin:round}
.it-seg{fill:none;stroke:#8a9a78;stroke-width:4;stroke-linecap:round;stroke-dasharray:7 7;transition:stroke .2s,stroke-width .2s}
.it-town circle{fill:#fff;stroke:#1f2a1f;stroke-width:3}
.it-town text{font-family:inherit;font-size:15px;font-weight:800;fill:#1f2a1f;paint-order:stroke;stroke:#efe9da;stroke-width:4px}
.fp-room rect{fill:#eef0e6;stroke:#1f2a1f;stroke-width:2;transition:fill .2s}
.fp-room text,.fp-static text{font-family:inherit;font-size:15px;font-weight:700;fill:#1f2a1f;text-anchor:middle}
.fp-static rect{fill:#e4e2d8;stroke:#1f2a1f;stroke-width:2}
.fp-door{font-family:inherit;font-size:12px;font-weight:700;fill:#55604f}
.fp-map svg{max-width:760px;margin-inline:auto}
.fp-way{fill:none;stroke:#0f766e;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:2 8;opacity:0;transition:opacity .2s}
.fp-you{fill:#0f766e;stroke:#fff;stroke-width:3}
@media (prefers-reduced-motion:reduce){.rt-line{animation:none;stroke-dashoffset:0}.it-seg,.fp-room rect,.fp-way{transition:none}}
.it:has(#it-d1:checked) .it-seg[data-day="1"],.it:has(#it-d2:checked) .it-seg[data-day="2"],.it:has(#it-d3:checked) .it-seg[data-day="3"],.it:has(#it-d4:checked) .it-seg[data-day="4"]{stroke:#c2410c;stroke-width:6;stroke-dasharray:none}
.fp:has(#fp-hall:checked) .fp-room.hall rect,.fp:has(#fp-wa:checked) .fp-room.wa rect,.fp:has(#fp-wb:checked) .fp-room.wb rect,.fp:has(#fp-cafe:checked) .fp-room.cafe rect,.fp:has(#fp-cloak:checked) .fp-room.cloak rect{fill:#99f6e4}
.fp:has(#fp-hall:checked) [data-way="hall"],.fp:has(#fp-wa:checked) [data-way="wa"],.fp:has(#fp-wb:checked) [data-way="wb"],.fp:has(#fp-cafe:checked) [data-way="cafe"],.fp:has(#fp-cloak:checked) [data-way="cloak"]{opacity:1}
.rt-sec{--bs-card-border-color:#e1dccd;color:#1f2a1f}
.rt-kicker{font-size:.75rem;letter-spacing:.12em;color:#9a3412}
.rt-stats>div{background:#f6f3ea}
.rt-stats dt{font-size:.72rem;letter-spacing:.08em;color:#55604f}
.rt-stats dd{font-size:1.15rem}
.rt-num{width:28px;height:28px;background:#c2410c;font-size:.8rem}
.rt-legs .leg{font-size:.8rem;color:#9a3412}
.form-check-input{width:18px;height:18px;border-color:#55604f}
.it .form-check-input:checked{background-color:#c2410c;border-color:#c2410c}
.fp .form-check-input:checked{background-color:#0f766e;border-color:#0f766e}
.form-check-input:focus-visible{box-shadow:none;outline:3px solid #1f2a1f;outline-offset:2px}
.it-day{grid-template-columns:auto 1fr;column-gap:12px;cursor:pointer;border-color:#e1dccd!important}
.it-day:has(input:checked){border-color:#c2410c!important;background:#fff7ed}
.fp-opt{cursor:pointer;border-color:#e1dccd!important}
.fp-opt:has(input:checked){border-color:#0f766e!important;background:#f0fdfa}
.muted{color:#55604f}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#55604f] uppercase">Route line that draws itself, with stages</span>
<section class="rounded-[22px] border border-[#e1dccd] bg-white p-4 text-[#1f2a1f] sm:p-6" aria-labelledby="rt-title">
  <p class="text-[.75rem] font-extrabold tracking-[.12em] uppercase text-[#9a3412]">Fishermen's Trail, Alentejo</p>
  <h2 id="rt-title" class="mt-1 text-[1.35rem] font-bold tracking-[-.02em]">Three days on the cliffs</h2>
  <div class="mt-4 grid gap-[22px] min-[880px]:grid-cols-[minmax(0,1.25fr)_minmax(0,1fr)] min-[880px]:items-start">
    <figure class="rt-map">
    <svg viewBox="0 0 600 400" role="img" aria-label="Illustrated map of a 57 km coastal trail from Porto Covo south to Zambujeira do Mar, with overnight stops in Vila Nova de Milfontes and Almograve.">
      <rect width="600" height="400" fill="#a9d3e0"/>
      <path d="M302 0C318 40 292 70 300 110C306 140 270 150 276 172C284 210 300 220 288 252C280 276 292 300 276 330C266 352 270 376 258 400H600V0Z" fill="#efe7d3"/>
      <path d="M302 0C318 40 292 70 300 110C306 140 270 150 276 172C284 210 300 220 288 252C280 276 292 300 276 330C266 352 270 376 258 400" fill="none" stroke="#f9f5ea" stroke-width="6"/>
      <path d="M296 166C340 172 380 150 430 160C470 168 520 150 600 158" fill="none" stroke="#a9d3e0" stroke-width="9" stroke-linecap="round"/>
      <g fill="#dfe8c8"><ellipse cx="450" cy="80" rx="90" ry="40"/><ellipse cx="470" cy="270" rx="100" ry="54"/><ellipse cx="400" cy="360" rx="70" ry="30"/></g>
      <g fill="none" stroke="#c9d6ad" stroke-width="1.5"><ellipse cx="450" cy="80" rx="60" ry="24"/><ellipse cx="470" cy="270" rx="66" ry="34"/><ellipse cx="470" cy="270" rx="34" ry="16"/></g>
      <g fill="#55604f" font-family="inherit" font-size="11" font-weight="700" letter-spacing="1.5"><text x="96" y="210">ATLANTIC OCEAN</text><text x="500" y="140">RIO MIRA</text></g>
      <g transform="translate(548 40)" fill="#1f2a1f"><path d="M0-18L6 4 0 0-6 4Z"/><text y="18" font-size="11" font-weight="800" text-anchor="middle" font-family="inherit">N</text></g>
      <g transform="translate(40 378)" fill="#1f2a1f" font-family="inherit" font-size="10" font-weight="700"><rect width="80" height="5" fill="none" stroke="#1f2a1f"/><rect width="40" height="5"/><text y="-6">5 km</text></g>
      <path d="M332 34C352 70 318 96 326 128C332 150 296 150 300 164C306 196 330 214 314 246C302 270 318 300 300 330C290 348 296 360 282 372" fill="none" stroke="#7a5c3b" stroke-width="3" stroke-dasharray="2 6" stroke-linecap="round" opacity=".55"/>
      <path class="rt-line" d="M332 34C352 70 318 96 326 128C332 150 296 150 300 164C306 196 330 214 314 246C302 270 318 300 300 330C290 348 296 360 282 372" pathLength="1"/>
      <g class="rt-stop"><circle cx="332" cy="34" r="9"/><text x="348" y="38">Porto Covo</text></g>
      <g class="rt-stop"><circle cx="300" cy="164" r="7"/><text x="316" y="168">Vila Nova de Milfontes</text></g>
      <g class="rt-stop"><circle cx="314" cy="246" r="7"/><text x="330" y="250">Almograve</text></g>
      <g class="rt-stop"><circle cx="282" cy="372" r="9"/><text x="298" y="376">Zambujeira do Mar</text></g>
    </svg>
    </figure>
    <div>
      <dl class="mt-3.5 grid grid-cols-3 gap-2"><div class="rounded-xl bg-[#f6f3ea] px-3 py-2.5"><dt class="text-[.72rem] font-bold tracking-[.08em] text-[#55604f] uppercase">Distance</dt><dd class="text-[1.15rem] font-extrabold">57 km</dd></div><div class="rounded-xl bg-[#f6f3ea] px-3 py-2.5"><dt class="text-[.72rem] font-bold tracking-[.08em] text-[#55604f] uppercase">Days</dt><dd class="text-[1.15rem] font-extrabold">3</dd></div><div class="rounded-xl bg-[#f6f3ea] px-3 py-2.5"><dt class="text-[.72rem] font-bold tracking-[.08em] text-[#55604f] uppercase">Ascent</dt><dd class="text-[1.15rem] font-extrabold">610 m</dd></div></dl>
      <ol class="mt-[18px] grid gap-3.5">
        <li class="relative pl-10"><span class="absolute top-0 left-0 grid size-7 place-items-center rounded-full bg-[#c2410c] text-[.8rem] font-extrabold text-white" aria-hidden="true">1</span><strong class="block text-[.95rem]">Porto Covo to Vila Nova de Milfontes</strong><span class="text-[.8rem] font-bold text-[#9a3412]">20 km · 6 h</span><p class="text-[.88rem] text-[#55604f]">Sandy paths along the cliffs, then the Mira estuary.</p></li>
        <li class="relative pl-10"><span class="absolute top-0 left-0 grid size-7 place-items-center rounded-full bg-[#c2410c] text-[.8rem] font-extrabold text-white" aria-hidden="true">2</span><strong class="block text-[.95rem]">Vila Nova de Milfontes to Almograve</strong><span class="text-[.8rem] font-bold text-[#9a3412]">15 km · 5 h</span><p class="text-[.88rem] text-[#55604f]">Ferry or bridge over the river, then dunes.</p></li>
        <li class="relative pl-10"><span class="absolute top-0 left-0 grid size-7 place-items-center rounded-full bg-[#c2410c] text-[.8rem] font-extrabold text-white" aria-hidden="true">3</span><strong class="block text-[.95rem]">Almograve to Zambujeira do Mar</strong><span class="text-[.8rem] font-bold text-[#9a3412]">22 km · 7 h</span><p class="text-[.88rem] text-[#55604f]">Stork nests on sea stacks and the Cavaleiro lighthouse.</p></li>
      </ol>
    </div>
  </div>
</section></div>
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#55604f] uppercase">Itinerary that highlights its leg on the map (CSS :has)</span>
<section class="rounded-[22px] border border-[#e1dccd] bg-white p-4 text-[#1f2a1f] sm:p-6" aria-labelledby="it-title">
  <p class="text-[.75rem] font-extrabold tracking-[.12em] uppercase text-[#9a3412]">Douro valley by train and boat</p>
  <h2 id="it-title" class="mt-1 text-[1.35rem] font-bold tracking-[-.02em]">Four days upriver</h2>
  <div class="mt-4 grid gap-[22px] min-[880px]:grid-cols-[minmax(0,1.25fr)_minmax(0,1fr)] min-[880px]:items-start">
    <figure class="it-map">
    <svg viewBox="0 0 600 300" role="img" aria-label="Map of the Douro valley route from Porto east to Pocinho in four day legs.">
      <rect width="600" height="300" fill="#efe9da"/>
      <g fill="#dbe5c3"><path d="M120 0C180 60 260 70 330 40S480 0 520 0Z"/><path d="M150 300C220 230 330 240 420 260S560 300 600 290V300Z"/></g>
      <path d="M0 196C90 170 160 214 250 166S330 214 380 196S430 156 470 162S540 190 600 172" fill="none" stroke="#9cc7da" stroke-width="12" stroke-linecap="round"/>
      <g fill="#55604f" font-family="inherit" font-size="11" font-weight="700" letter-spacing="1.5"><text x="120" y="226">RIO DOURO</text></g>
      <path class="it-seg in-has-[#it-d1:checked]:[stroke-dasharray:none] in-has-[#it-d1:checked]:[stroke-width:6] in-has-[#it-d1:checked]:stroke-[#c2410c]" data-day="1" d="M40 172C110 150 170 186 248 146"/>
      <path class="it-seg in-has-[#it-d2:checked]:[stroke-dasharray:none] in-has-[#it-d2:checked]:[stroke-width:6] in-has-[#it-d2:checked]:stroke-[#c2410c]" data-day="2" d="M248 146C300 120 320 190 372 178"/>
      <path class="it-seg in-has-[#it-d3:checked]:[stroke-dasharray:none] in-has-[#it-d3:checked]:[stroke-width:6] in-has-[#it-d3:checked]:stroke-[#c2410c]" data-day="3" d="M372 178C410 170 420 140 462 146"/>
      <path class="it-seg in-has-[#it-d4:checked]:[stroke-dasharray:none] in-has-[#it-d4:checked]:[stroke-width:6] in-has-[#it-d4:checked]:stroke-[#c2410c]" data-day="4" d="M462 146C500 150 520 180 566 160"/>
      <g class="it-town"><circle cx="40" cy="172" r="7"/><text x="40" y="156" text-anchor="start">Porto</text></g>
      <g class="it-town"><circle cx="248" cy="146" r="7"/><text x="248" y="130" text-anchor="middle">Peso da Régua</text></g>
      <g class="it-town"><circle cx="372" cy="178" r="7"/><text x="372" y="162" text-anchor="middle">Pinhão</text></g>
      <g class="it-town"><circle cx="462" cy="146" r="7"/><text x="462" y="130" text-anchor="middle">Tua</text></g>
      <g class="it-town"><circle cx="566" cy="160" r="7"/><text x="566" y="144" text-anchor="end">Pocinho</text></g>
    </svg>
    </figure>
    <fieldset class="grid gap-2">
      <legend class="mb-2 text-[.8rem] font-extrabold tracking-[.08em] text-[#55604f] uppercase">Show a day on the map</legend>
      <label class="grid cursor-pointer grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 rounded-[14px] border border-[#e1dccd] px-3.5 py-3 transition-colors has-checked:border-[#c2410c] has-checked:bg-[#fff7ed] motion-reduce:transition-none"><input class="row-span-2 mt-[3px] size-[18px] accent-[#c2410c] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-[#1f2a1f]" type="radio" name="it-day" id="it-d1" checked><b class="text-[.92rem]"><span class="text-[#9a3412]">Day 1</span> · Porto to Peso da Régua</b><small class="text-[.83rem] text-[#55604f]">Train along the river, 2 h. Evening at a quinta above the town.</small></label>
      <label class="grid cursor-pointer grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 rounded-[14px] border border-[#e1dccd] px-3.5 py-3 transition-colors has-checked:border-[#c2410c] has-checked:bg-[#fff7ed] motion-reduce:transition-none"><input class="row-span-2 mt-[3px] size-[18px] accent-[#c2410c] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-[#1f2a1f]" type="radio" name="it-day" id="it-d2"><b class="text-[.92rem]"><span class="text-[#9a3412]">Day 2</span> · Peso da Régua to Pinhão</b><small class="text-[.83rem] text-[#55604f]">Rabelo boat cruise, 1 h 30 min, with a stop at the Bagaúste lock.</small></label>
      <label class="grid cursor-pointer grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 rounded-[14px] border border-[#e1dccd] px-3.5 py-3 transition-colors has-checked:border-[#c2410c] has-checked:bg-[#fff7ed] motion-reduce:transition-none"><input class="row-span-2 mt-[3px] size-[18px] accent-[#c2410c] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-[#1f2a1f]" type="radio" name="it-day" id="it-d3"><b class="text-[.92rem]"><span class="text-[#9a3412]">Day 3</span> · Pinhão to Tua</b><small class="text-[.83rem] text-[#55604f]">Short train hop, then a 9 km walk back through the terraces.</small></label>
      <label class="grid cursor-pointer grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 rounded-[14px] border border-[#e1dccd] px-3.5 py-3 transition-colors has-checked:border-[#c2410c] has-checked:bg-[#fff7ed] motion-reduce:transition-none"><input class="row-span-2 mt-[3px] size-[18px] accent-[#c2410c] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-[#1f2a1f]" type="radio" name="it-day" id="it-d4"><b class="text-[.92rem]"><span class="text-[#9a3412]">Day 4</span> · Tua to Pocinho</b><small class="text-[.83rem] text-[#55604f]">End of the line: almond groves and the Côa valley rock art.</small></label>
    </fieldset>
  </div>
</section></div>
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#55604f] uppercase">Floor plan with a way-finding line (CSS :has)</span>
<section class="rounded-[22px] border border-[#e1dccd] bg-white p-4 text-[#1f2a1f] sm:p-6" aria-labelledby="fp-title">
  <p class="text-[.75rem] font-extrabold tracking-[.12em] uppercase text-[#0f766e]">Ground floor</p>
  <h2 id="fp-title" class="mt-1 text-[1.35rem] font-bold tracking-[-.02em]">Find your room</h2>
  <fieldset class="mt-3.5">
    <legend class="mb-2 text-[.8rem] font-extrabold tracking-[.08em] text-[#55604f] uppercase">Show the way to</legend>
    <div class="flex flex-wrap gap-2">
      <label class="inline-flex cursor-pointer items-center gap-2 rounded-full border border-[#e1dccd] py-2 pr-3.5 pl-2.5 text-[.9rem] font-semibold has-checked:border-[#0f766e] has-checked:bg-[#f0fdfa]"><input class="size-[18px] accent-[#0f766e] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-[#1f2a1f]" type="radio" name="fp-room" id="fp-hall">Main hall</label>
      <label class="inline-flex cursor-pointer items-center gap-2 rounded-full border border-[#e1dccd] py-2 pr-3.5 pl-2.5 text-[.9rem] font-semibold has-checked:border-[#0f766e] has-checked:bg-[#f0fdfa]"><input class="size-[18px] accent-[#0f766e] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-[#1f2a1f]" type="radio" name="fp-room" id="fp-wa" checked>Workshop A</label>
      <label class="inline-flex cursor-pointer items-center gap-2 rounded-full border border-[#e1dccd] py-2 pr-3.5 pl-2.5 text-[.9rem] font-semibold has-checked:border-[#0f766e] has-checked:bg-[#f0fdfa]"><input class="size-[18px] accent-[#0f766e] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-[#1f2a1f]" type="radio" name="fp-room" id="fp-wb">Workshop B</label>
      <label class="inline-flex cursor-pointer items-center gap-2 rounded-full border border-[#e1dccd] py-2 pr-3.5 pl-2.5 text-[.9rem] font-semibold has-checked:border-[#0f766e] has-checked:bg-[#f0fdfa]"><input class="size-[18px] accent-[#0f766e] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-[#1f2a1f]" type="radio" name="fp-room" id="fp-cafe">Café</label>
      <label class="inline-flex cursor-pointer items-center gap-2 rounded-full border border-[#e1dccd] py-2 pr-3.5 pl-2.5 text-[.9rem] font-semibold has-checked:border-[#0f766e] has-checked:bg-[#f0fdfa]"><input class="size-[18px] accent-[#0f766e] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-[#1f2a1f]" type="radio" name="fp-room" id="fp-cloak">Cloakroom</label>
    </div>
  </fieldset>
  <figure class="fp-map mt-4">
    <svg viewBox="0 0 600 360" role="img" aria-label="Floor plan of the ground floor with the main hall, two workshop rooms, a café, a cloakroom and toilets. The entrance is at the bottom right.">
      <rect x="20" y="20" width="560" height="320" rx="6" fill="#fbfaf5" stroke="#1f2a1f" stroke-width="4"/>
      <g class="fp-room [&_rect]:in-has-[#fp-hall:checked]:fill-[#99f6e4]"><rect x="40" y="40" width="300" height="170" rx="4"/><text x="190" y="129">Main hall</text></g>
      <g class="fp-room [&_rect]:in-has-[#fp-wa:checked]:fill-[#99f6e4]"><rect x="360" y="40" width="200" height="100" rx="4"/><text x="460" y="94">Workshop A</text></g>
      <g class="fp-room [&_rect]:in-has-[#fp-wb:checked]:fill-[#99f6e4]"><rect x="360" y="160" width="200" height="100" rx="4"/><text x="460" y="214">Workshop B</text></g>
      <g class="fp-room [&_rect]:in-has-[#fp-cafe:checked]:fill-[#99f6e4]"><rect x="40" y="230" width="180" height="110" rx="4"/><text x="130" y="289">Café</text></g>
      <g class="fp-room [&_rect]:in-has-[#fp-cloak:checked]:fill-[#99f6e4]"><rect x="240" y="280" width="100" height="60" rx="4"/><text x="290" y="314">Cloakroom</text></g>
      <g class="fp-static"><rect x="360" y="280" width="90" height="60" rx="4"/><text x="405" y="314">Toilets</text></g>
      <path d="M470 340h70" stroke="#fbfaf5" stroke-width="6"/>
      <text x="505" y="356" class="fp-door" text-anchor="middle">Entrance</text>
      <path class="fp-way in-has-[#fp-hall:checked]:opacity-100" data-way="hall" d="M505 322V270H350V220H190V210"/>
      <path class="fp-way in-has-[#fp-wa:checked]:opacity-100" data-way="wa" d="M505 322V270H350V90H360"/>
      <path class="fp-way in-has-[#fp-wb:checked]:opacity-100" data-way="wb" d="M505 322V270H460V260"/>
      <path class="fp-way in-has-[#fp-cafe:checked]:opacity-100" data-way="cafe" d="M505 322V270H230V292H220"/>
      <path class="fp-way in-has-[#fp-cloak:checked]:opacity-100" data-way="cloak" d="M505 322V270H290V280"/>
      <circle class="fp-you" cx="505" cy="322" r="8"/>
    </svg>
  </figure>
  <p class="mt-2.5 text-[.82rem] text-[#55604f]">The dotted line starts at the entrance, marked with a green dot.</p>
</section></div>
CSS
@layer components {
/* Illustrated maps (not to scale): a route drawn with stroke-dashoffset, an itinerary that highlights its leg, and a floor plan. */
.rt-map svg,.it-map svg,.fp-map svg{display:block;width:100%;height:auto;border-radius:16px}
.rt-line{fill:none;stroke:#c2410c;stroke-width:4.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:1;stroke-dashoffset:1;animation:rt-draw 3.2s ease-in-out .3s forwards}
@keyframes rt-draw{to{stroke-dashoffset:0}}
.rt-stop circle{fill:#fff;stroke:#c2410c;stroke-width:4}
.rt-stop text{font-family:inherit;font-size:15px;font-weight:800;fill:#1f2a1f;paint-order:stroke;stroke:#efe7d3;stroke-width:4px;stroke-linejoin:round}
.it-seg{fill:none;stroke:#8a9a78;stroke-width:4;stroke-linecap:round;stroke-dasharray:7 7;transition:stroke .2s,stroke-width .2s}
.it-town circle{fill:#fff;stroke:#1f2a1f;stroke-width:3}
.it-town text{font-family:inherit;font-size:15px;font-weight:800;fill:#1f2a1f;paint-order:stroke;stroke:#efe9da;stroke-width:4px}
.fp-room rect{fill:#eef0e6;stroke:#1f2a1f;stroke-width:2;transition:fill .2s}
.fp-room text,.fp-static text{font-family:inherit;font-size:15px;font-weight:700;fill:#1f2a1f;text-anchor:middle}
.fp-static rect{fill:#e4e2d8;stroke:#1f2a1f;stroke-width:2}
.fp-door{font-family:inherit;font-size:12px;font-weight:700;fill:#55604f}
.fp-map svg{max-width:760px;margin-inline:auto}
.fp-way{fill:none;stroke:#0f766e;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:2 8;opacity:0;transition:opacity .2s}
.fp-you{fill:#0f766e;stroke:#fff;stroke-width:3}
@media (prefers-reduced-motion:reduce){.rt-line{animation:none;stroke-dashoffset:0}.it-seg,.fp-room rect,.fp-way{transition:none}}
}

About this set

Three illustrated maps for travel, events and venues, built from inline SVG and CSS with no JavaScript. The trail map draws a three-day coastal walk from Porto Covo to Zambujeira do Mar: the route line draws itself once with stroke-dashoffset on a pathLength of 1, over a dotted guide path, with the stops labelled, a compass and a scale bar, and a stats row and numbered stage list beside it. The itinerary map follows a four-day trip up the Douro valley; each day is a radio button with a short description, and the checked day turns its leg of the route solid and orange through :has(), so the list and the map stay in step without a script. The floor plan shows a ground floor with a main hall, two workshop rooms, a café, a cloakroom and toilets; choosing a room from a row of radio pills fills that room and shows a dotted way-finding line from the entrance. Use them for walking routes, tour itineraries, conference venues and office maps, where a hand-drawn map is clearer than a street map.

What’s included

  • Route line drawn once with stroke-dashoffset and pathLength
  • Day itinerary that highlights its route leg with :has()
  • Floor plan with room highlight and way-finding line
  • No JavaScript in any variant
  • Labels with a paint-order halo for legibility on the map

Accessibility

Each map is an image with a text description, and the details are in real text beside it: the stage list, the day descriptions and the room names. The itinerary and floor plan controls are native radio buttons in fieldsets with legends, so they work with the keyboard and are announced correctly, and they show a focus outline. Under prefers-reduced-motion the route appears already drawn and the highlight transitions are removed.

Customise it

Change the route colour in .rt-line and .rt-stop circle, the itinerary highlight in the :has() rule for .it-seg, and the room highlight and way-finding colours in the .fp rules. Replace the SVG paths with your own route, river or rooms.