Open demo

CSS code

HTML
<div class="mc"><span class="lbl">Bubble and tail as one shape()</span>
<div class="mc-stage">
  <div class="mc-shape"><p><span class="sr-only">Mira: </span>The ferry to Hvar leaves at 14:30. Shall I grab two tickets?</p></div>
  <div class="mc-shape me"><p><span class="sr-only">You: </span>Yes please, and a table on the top deck if they still do those.</p></div>
</div>
<p class="note">One <code>clip-path: shape()</code> per bubble draws corners and curved tail together.</p></div>

<div class="mc"><span class="lbl">Squircle bubbles with scooped tails</span>
<div class="mc-stage">
  <p class="mc-sq"><span class="sr-only">Mira: </span>Top deck is first come, first served. We'd need to board by 14:00.</p>
  <p class="mc-sq me"><span class="sr-only">You: </span>Then let's meet at the kiosk on the pier at 13:45.</p>
</div>
<p class="note"><code>corner-shape: squircle</code> on the bubble, <code>corner-shape: scoop</code> on the tail.</p></div>

<div class="mc full"><span class="lbl">Anchored bubble that keeps its tail on the anchor</span>
<div class="mc-cabin" role="radiogroup" aria-labelledby="mc-cabin-h">
  <p class="mc-h" id="mc-cabin-h">Pick a seat, rows 1 to 4</p>
  <div class="mc-seats">
    <div class="mc-row">
      <span class="mc-seat"><input type="radio" name="mc-seat" id="mc-1a" checked><label for="mc-1a">1A<span class="sr-only">, window, extra legroom, €18</span></label><span class="mc-bub" aria-hidden="true"><span class="mc-bub-in"><b>1A · Window</b>Extra legroom, €18</span></span></span>
      <span class="mc-seat"><input type="radio" name="mc-seat" id="mc-1b" disabled><label for="mc-1b">1B<span class="sr-only">, taken</span></label></span>
      <span class="mc-aisle" aria-hidden="true">1</span>
      <span class="mc-seat"><input type="radio" name="mc-seat" id="mc-1c" disabled><label for="mc-1c">1C<span class="sr-only">, taken</span></label></span>
      <span class="mc-seat"><input type="radio" name="mc-seat" id="mc-1d"><label for="mc-1d">1D<span class="sr-only">, window, extra legroom, €18</span></label><span class="mc-bub" aria-hidden="true"><span class="mc-bub-in"><b>1D · Window</b>Extra legroom, €18</span></span></span>
    </div>
    <div class="mc-row">
      <span class="mc-seat"><input type="radio" name="mc-seat" id="mc-2a" disabled><label for="mc-2a">2A<span class="sr-only">, taken</span></label></span>
      <span class="mc-seat"><input type="radio" name="mc-seat" id="mc-2b"><label for="mc-2b">2B<span class="sr-only">, aisle, €9</span></label><span class="mc-bub" aria-hidden="true"><span class="mc-bub-in"><b>2B · Aisle</b>Standard seat, €9</span></span></span>
      <span class="mc-aisle" aria-hidden="true">2</span>
      <span class="mc-seat"><input type="radio" name="mc-seat" id="mc-2c" disabled><label for="mc-2c">2C<span class="sr-only">, taken</span></label></span>
      <span class="mc-seat"><input type="radio" name="mc-seat" id="mc-2d" disabled><label for="mc-2d">2D<span class="sr-only">, taken</span></label></span>
    </div>
    <div class="mc-row">
      <span class="mc-seat"><input type="radio" name="mc-seat" id="mc-3a"><label for="mc-3a">3A<span class="sr-only">, window, €9</span></label><span class="mc-bub" aria-hidden="true"><span class="mc-bub-in"><b>3A · Window</b>Standard seat, €9</span></span></span>
      <span class="mc-seat"><input type="radio" name="mc-seat" id="mc-3b" disabled><label for="mc-3b">3B<span class="sr-only">, taken</span></label></span>
      <span class="mc-aisle" aria-hidden="true">3</span>
      <span class="mc-seat"><input type="radio" name="mc-seat" id="mc-3c"><label for="mc-3c">3C<span class="sr-only">, aisle, €9</span></label><span class="mc-bub" aria-hidden="true"><span class="mc-bub-in"><b>3C · Aisle</b>Standard seat, €9</span></span></span>
      <span class="mc-seat"><input type="radio" name="mc-seat" id="mc-3d" disabled><label for="mc-3d">3D<span class="sr-only">, taken</span></label></span>
    </div>
    <div class="mc-row">
      <span class="mc-seat"><input type="radio" name="mc-seat" id="mc-4a" disabled><label for="mc-4a">4A<span class="sr-only">, taken</span></label></span>
      <span class="mc-seat"><input type="radio" name="mc-seat" id="mc-4b" disabled><label for="mc-4b">4B<span class="sr-only">, taken</span></label></span>
      <span class="mc-aisle" aria-hidden="true">4</span>
      <span class="mc-seat"><input type="radio" name="mc-seat" id="mc-4c" disabled><label for="mc-4c">4C<span class="sr-only">, taken</span></label></span>
      <span class="mc-seat"><input type="radio" name="mc-seat" id="mc-4d"><label for="mc-4d">4D<span class="sr-only">, window, €9</span></label><span class="mc-bub" aria-hidden="true"><span class="mc-bub-in"><b>4D · Window</b>Standard seat, €9</span></span></span>
    </div>
  </div>
</div>
<p class="note">Pick 1A, then 4D: the bubble flips below or shifts sideways when it runs out of room, and <code>@container anchored(fallback: …)</code> moves the tail with it.</p></div>
CSS
/* Modern CSS speech bubbles. Each technique sits behind @supports, and the fallback is a classic bubble. */
.mc{--mc-ink:#1b1a17;--mc-teal:#0e7468;--mc-plum:#5b2a86;--mc-card:#fff;--mc-line:#d9d1c4}
.mc .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.mc-stage{display:flex;flex-direction:column;gap:18px;padding:26px 22px;border:1px solid var(--mc-line);border-radius:20px;background:#fbf9f5}

/* 1. Bubble and tail as one clip-path: shape(). Fallback: border-radius plus a border-triangle tail. */
.mc-shape{filter:drop-shadow(0 8px 12px rgba(27,26,23,.16))}
.mc-shape p{position:relative;width:fit-content;max-width:88%;margin:0 0 16px;padding:14px 18px;border-radius:18px;background:var(--mc-teal);color:#fff;font-size:.95rem;line-height:1.45}
.mc-shape p::after{content:"";position:absolute;top:100%;left:22px;border:solid transparent;border-width:14px 14px 0 0;border-top-color:var(--mc-teal)}
.mc-shape.me{align-self:flex-end}
.mc-shape.me p{margin-left:auto;background:var(--mc-plum)}
.mc-shape.me p::after{left:auto;right:22px;border-width:14px 0 0 14px;border-top-color:var(--mc-plum)}
@supports (clip-path:shape(from 0 0,line to 1px 1px)){
  .mc-shape p{margin-bottom:0;padding-bottom:32px;border-radius:0;
    clip-path:shape(from 18px 0,hline to calc(100% - 18px),curve to 100% 18px with 100% 0,vline to calc(100% - 36px),
      curve to calc(100% - 18px) calc(100% - 18px) with 100% calc(100% - 18px),hline to 60px,
      curve to 10px 100% with 34px calc(100% - 4px),curve to 26px calc(100% - 18px) with 24px calc(100% - 8px),
      hline to 18px,curve to 0 calc(100% - 36px) with 0 calc(100% - 18px),vline to 18px,curve to 18px 0 with 0 0,close)}
  .mc-shape.me p{clip-path:shape(from calc(100% - 18px) 0,hline to 18px,curve to 0 18px with 0 0,vline to calc(100% - 36px),
      curve to 18px calc(100% - 18px) with 0 calc(100% - 18px),hline to calc(100% - 60px),
      curve to calc(100% - 10px) 100% with calc(100% - 34px) calc(100% - 4px),curve to calc(100% - 26px) calc(100% - 18px) with calc(100% - 24px) calc(100% - 8px),
      hline to calc(100% - 18px),curve to 100% calc(100% - 36px) with 100% calc(100% - 18px),vline to 18px,curve to calc(100% - 18px) 0 with 100% 0,close)}
  .mc-shape p::after{display:none}
}

/* 2. corner-shape: squircle corners, and a tail that is one corner with corner-shape: scoop, so its border
   follows the curve. Fallback: round corners and a radial-gradient tail. */
.mc-sq{position:relative;width:fit-content;max-width:86%;margin:0 0 0 12px;padding:14px 18px;border:1.5px solid var(--mc-ink);border-radius:24px 24px 24px 0;background:var(--mc-card);
  font-size:.95rem;line-height:1.45}
.mc-sq::after{content:"";position:absolute;bottom:-1.5px;left:-13.5px;width:15px;height:20px;background:radial-gradient(12px 20px at 0 0,transparent 96%,var(--mc-ink) 100%)}
.mc-sq.me{align-self:flex-end;margin:0 12px 0 0;border-color:var(--mc-plum);border-radius:24px 24px 0 24px;background:var(--mc-plum);color:#fff}
.mc-sq.me::after{left:auto;right:-13.5px;background:radial-gradient(12px 20px at 100% 0,transparent 96%,var(--mc-plum) 100%)}
@supports (corner-shape:scoop){
  .mc-sq{corner-shape:squircle;border-radius:34px 34px 34px 0}
  .mc-sq.me{border-radius:34px 34px 0 34px}
  .mc-sq::after{background:var(--mc-card);border:solid var(--mc-ink);border-width:1.5px 0 1.5px 1.5px;border-top-left-radius:100% 100%;corner-shape:scoop}
  .mc-sq.me::after{background:var(--mc-plum);border-color:var(--mc-plum);border-width:1.5px 1.5px 1.5px 0;border-top-left-radius:0;border-top-right-radius:100% 100%}
}

/* 3. Anchor positioning: the checked seat becomes the anchor (anchor-scope keeps one name per seat), the bubble
   tries above, below and shifted placements, and an anchored container query moves the tail to match. */
.mc-cabin{position:relative;display:flex;flex-direction:column;align-items:center;gap:14px;margin:0;padding:24px 16px 30px;border:1px solid var(--mc-line);border-radius:20px;background:#fbf9f5;min-width:0}
.mc-h{margin:0 0 6px;text-align:center;font-weight:700;font-size:.95rem}
.mc-seats{display:grid;grid-template-columns:repeat(2,40px) 24px repeat(2,40px);gap:8px}
.mc-row{display:contents}
.mc-aisle{display:grid;place-items:center;font-size:.72rem;color:#5d574d}
.mc-seat{anchor-scope:--seat}
.mc-seat input{position:absolute;opacity:0;width:1px;height:1px}
.mc-seat label{display:grid;place-items:center;width:40px;height:40px;border:1.5px solid #8c8476;border-radius:10px 10px 6px 6px;background:#fff;font-size:.78rem;font-weight:700;color:var(--mc-ink);cursor:pointer}
.mc-seat label:hover{border-color:var(--mc-teal)}
.mc-seat input:checked+label{anchor-name:--seat;border-color:var(--mc-teal);background:var(--mc-teal);color:#fff}
.mc-seat input:focus-visible+label{outline:2px solid var(--mc-teal);outline-offset:3px}
.mc-seat input:disabled+label{border-style:dashed;border-color:#b3ab9d;background:repeating-linear-gradient(-45deg,#ece6db 0 4px,#f7f3ec 4px 8px);color:#6e675b;cursor:not-allowed}
.mc-bub{display:none;position:absolute;z-index:2;position-anchor:--seat;position-area:top;justify-self:unsafe anchor-center;margin-bottom:12px;width:200px;container-type:anchored;
  position-try-fallbacks:--mc-b,--mc-tr,--mc-tl,--mc-br,--mc-bl}
.mc-seat input:checked~.mc-bub{display:block}
@position-try --mc-b{position-area:bottom;justify-self:unsafe anchor-center;margin:12px 0 0}
@position-try --mc-tr{position-area:top span-right;justify-self:normal;margin:0 0 12px}
@position-try --mc-tl{position-area:top span-left;justify-self:normal;margin:0 0 12px}
@position-try --mc-br{position-area:bottom span-right;justify-self:normal;margin:12px 0 0}
@position-try --mc-bl{position-area:bottom span-left;justify-self:normal;margin:12px 0 0}
.mc-bub-in{position:relative;display:block;padding:10px 13px;border-radius:12px;background:var(--mc-ink);color:#fff;font-size:.8rem;line-height:1.4;box-shadow:0 12px 26px -12px rgba(27,26,23,.6)}
.mc-bub-in b{display:block;font-size:.86rem}
.mc-bub-in::after{content:"";position:absolute;top:100%;left:50%;width:14px;height:8px;translate:-50% 0;background:inherit;clip-path:polygon(0 0,100% 0,50% 100%)}
@container anchored(fallback:--mc-b){.mc-bub-in::after{top:auto;bottom:100%;clip-path:polygon(50% 0,100% 100%,0 100%)}}
@container anchored(fallback:--mc-tr){.mc-bub-in::after{left:20px}}
@container anchored(fallback:--mc-tl){.mc-bub-in::after{left:auto;right:20px;translate:50% 0}}
@container anchored(fallback:--mc-br){.mc-bub-in::after{top:auto;bottom:100%;left:20px;clip-path:polygon(50% 0,100% 100%,0 100%)}}
@container anchored(fallback:--mc-bl){.mc-bub-in::after{top:auto;bottom:100%;left:auto;right:20px;translate:50% 0;clip-path:polygon(50% 0,100% 100%,0 100%)}}
/* Anchor positioning without anchored queries: no fallbacks, so the bubble stays above with its tail centred */
@supports not (container-type:anchored){.mc-bub{position-try-fallbacks:none}}
/* No anchor positioning: the bubble sits above its own seat */
@supports not (anchor-name:--seat){
  .mc-seat{position:relative}
  .mc-bub{bottom:100%;left:50%;translate:-50% 0;width:180px}
}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div class="mc"><span class="lbl">Bubble and tail as one shape()</span>
<div class="mc-stage card d-flex flex-column gap-3">
  <div class="mc-shape"><p><span class="visually-hidden">Mira: </span>The ferry to Hvar leaves at 14:30. Shall I grab two tickets?</p></div>
  <div class="mc-shape me"><p><span class="visually-hidden">You: </span>Yes please, and a table on the top deck if they still do those.</p></div>
</div>
<p class="note">One <code>clip-path: shape()</code> per bubble draws corners and curved tail together.</p></div>

<div class="mc"><span class="lbl">Squircle bubbles with scooped tails</span>
<div class="mc-stage card d-flex flex-column gap-3">
  <p class="mc-sq mb-0"><span class="visually-hidden">Mira: </span>Top deck is first come, first served. We'd need to board by 14:00.</p>
  <p class="mc-sq me mb-0"><span class="visually-hidden">You: </span>Then let's meet at the kiosk on the pier at 13:45.</p>
</div>
<p class="note"><code>corner-shape: squircle</code> on the bubble, <code>corner-shape: scoop</code> on the tail.</p></div>

<div class="mc full"><span class="lbl">Anchored bubble that keeps its tail on the anchor</span>
<div class="mc-cabin card align-items-center" role="radiogroup" aria-labelledby="mc-cabin-h">
  <p class="mc-h fw-bold text-center mb-1" id="mc-cabin-h">Pick a seat, rows 1 to 4</p>
  <div class="mc-seats">
    <div class="mc-row">
      <span class="mc-seat"><input class="btn-check" type="radio" name="mc-seat" id="mc-1a" checked><label for="mc-1a">1A<span class="visually-hidden">, window, extra legroom, €18</span></label><span class="mc-bub" aria-hidden="true"><span class="mc-bub-in"><b>1A · Window</b>Extra legroom, €18</span></span></span>
      <span class="mc-seat"><input class="btn-check" type="radio" name="mc-seat" id="mc-1b" disabled><label for="mc-1b">1B<span class="visually-hidden">, taken</span></label></span>
      <span class="mc-aisle" aria-hidden="true">1</span>
      <span class="mc-seat"><input class="btn-check" type="radio" name="mc-seat" id="mc-1c" disabled><label for="mc-1c">1C<span class="visually-hidden">, taken</span></label></span>
      <span class="mc-seat"><input class="btn-check" type="radio" name="mc-seat" id="mc-1d"><label for="mc-1d">1D<span class="visually-hidden">, window, extra legroom, €18</span></label><span class="mc-bub" aria-hidden="true"><span class="mc-bub-in"><b>1D · Window</b>Extra legroom, €18</span></span></span>
    </div>
    <div class="mc-row">
      <span class="mc-seat"><input class="btn-check" type="radio" name="mc-seat" id="mc-2a" disabled><label for="mc-2a">2A<span class="visually-hidden">, taken</span></label></span>
      <span class="mc-seat"><input class="btn-check" type="radio" name="mc-seat" id="mc-2b"><label for="mc-2b">2B<span class="visually-hidden">, aisle, €9</span></label><span class="mc-bub" aria-hidden="true"><span class="mc-bub-in"><b>2B · Aisle</b>Standard seat, €9</span></span></span>
      <span class="mc-aisle" aria-hidden="true">2</span>
      <span class="mc-seat"><input class="btn-check" type="radio" name="mc-seat" id="mc-2c" disabled><label for="mc-2c">2C<span class="visually-hidden">, taken</span></label></span>
      <span class="mc-seat"><input class="btn-check" type="radio" name="mc-seat" id="mc-2d" disabled><label for="mc-2d">2D<span class="visually-hidden">, taken</span></label></span>
    </div>
    <div class="mc-row">
      <span class="mc-seat"><input class="btn-check" type="radio" name="mc-seat" id="mc-3a"><label for="mc-3a">3A<span class="visually-hidden">, window, €9</span></label><span class="mc-bub" aria-hidden="true"><span class="mc-bub-in"><b>3A · Window</b>Standard seat, €9</span></span></span>
      <span class="mc-seat"><input class="btn-check" type="radio" name="mc-seat" id="mc-3b" disabled><label for="mc-3b">3B<span class="visually-hidden">, taken</span></label></span>
      <span class="mc-aisle" aria-hidden="true">3</span>
      <span class="mc-seat"><input class="btn-check" type="radio" name="mc-seat" id="mc-3c"><label for="mc-3c">3C<span class="visually-hidden">, aisle, €9</span></label><span class="mc-bub" aria-hidden="true"><span class="mc-bub-in"><b>3C · Aisle</b>Standard seat, €9</span></span></span>
      <span class="mc-seat"><input class="btn-check" type="radio" name="mc-seat" id="mc-3d" disabled><label for="mc-3d">3D<span class="visually-hidden">, taken</span></label></span>
    </div>
    <div class="mc-row">
      <span class="mc-seat"><input class="btn-check" type="radio" name="mc-seat" id="mc-4a" disabled><label for="mc-4a">4A<span class="visually-hidden">, taken</span></label></span>
      <span class="mc-seat"><input class="btn-check" type="radio" name="mc-seat" id="mc-4b" disabled><label for="mc-4b">4B<span class="visually-hidden">, taken</span></label></span>
      <span class="mc-aisle" aria-hidden="true">4</span>
      <span class="mc-seat"><input class="btn-check" type="radio" name="mc-seat" id="mc-4c" disabled><label for="mc-4c">4C<span class="visually-hidden">, taken</span></label></span>
      <span class="mc-seat"><input class="btn-check" type="radio" name="mc-seat" id="mc-4d"><label for="mc-4d">4D<span class="visually-hidden">, window, €9</span></label><span class="mc-bub" aria-hidden="true"><span class="mc-bub-in"><b>4D · Window</b>Standard seat, €9</span></span></span>
    </div>
  </div>
</div>
<p class="note">Pick 1A, then 4D: the bubble flips below or shifts sideways when it runs out of room, and <code>@container anchored(fallback: …)</code> moves the tail with it.</p></div>
CSS
/* Modern CSS speech bubbles on Bootstrap (.card stages, .btn-check radios, .visually-hidden). Each technique sits behind @supports, and the fallback is a classic bubble. */
.mc{--bs-card-border-color:var(--mc-line);--bs-card-border-radius:20px;--bs-card-bg:#fbf9f5;--mc-ink:#1b1a17;--mc-teal:#0e7468;--mc-plum:#5b2a86;--mc-card:#fff;--mc-line:#d9d1c4}
.mc-stage{display:flex;flex-direction:column;gap:18px;padding:26px 22px;border:1px solid var(--mc-line);border-radius:20px;background:#fbf9f5}

/* 1. Bubble and tail as one clip-path: shape(). Fallback: border-radius plus a border-triangle tail. */
.mc-shape{filter:drop-shadow(0 8px 12px rgba(27,26,23,.16))}
.mc-shape p{position:relative;width:fit-content;max-width:88%;margin:0 0 16px;padding:14px 18px;border-radius:18px;background:var(--mc-teal);color:#fff;font-size:.95rem;line-height:1.45}
.mc-shape p::after{content:"";position:absolute;top:100%;left:22px;border:solid transparent;border-width:14px 14px 0 0;border-top-color:var(--mc-teal)}
.mc-shape.me{align-self:flex-end}
.mc-shape.me p{margin-left:auto;background:var(--mc-plum)}
.mc-shape.me p::after{left:auto;right:22px;border-width:14px 0 0 14px;border-top-color:var(--mc-plum)}
@supports (clip-path:shape(from 0 0,line to 1px 1px)){
  .mc-shape p{margin-bottom:0;padding-bottom:32px;border-radius:0;
    clip-path:shape(from 18px 0,hline to calc(100% - 18px),curve to 100% 18px with 100% 0,vline to calc(100% - 36px),
      curve to calc(100% - 18px) calc(100% - 18px) with 100% calc(100% - 18px),hline to 60px,
      curve to 10px 100% with 34px calc(100% - 4px),curve to 26px calc(100% - 18px) with 24px calc(100% - 8px),
      hline to 18px,curve to 0 calc(100% - 36px) with 0 calc(100% - 18px),vline to 18px,curve to 18px 0 with 0 0,close)}
  .mc-shape.me p{clip-path:shape(from calc(100% - 18px) 0,hline to 18px,curve to 0 18px with 0 0,vline to calc(100% - 36px),
      curve to 18px calc(100% - 18px) with 0 calc(100% - 18px),hline to calc(100% - 60px),
      curve to calc(100% - 10px) 100% with calc(100% - 34px) calc(100% - 4px),curve to calc(100% - 26px) calc(100% - 18px) with calc(100% - 24px) calc(100% - 8px),
      hline to calc(100% - 18px),curve to 100% calc(100% - 36px) with 100% calc(100% - 18px),vline to 18px,curve to calc(100% - 18px) 0 with 100% 0,close)}
  .mc-shape p::after{display:none}
}

/* 2. corner-shape: squircle corners, and a tail that is one corner with corner-shape: scoop, so its border
   follows the curve. Fallback: round corners and a radial-gradient tail. */
.mc-sq{position:relative;width:fit-content;max-width:86%;margin:0 0 0 12px;padding:14px 18px;border:1.5px solid var(--mc-ink);border-radius:24px 24px 24px 0;background:var(--mc-card);
  font-size:.95rem;line-height:1.45}
.mc-sq::after{content:"";position:absolute;bottom:-1.5px;left:-13.5px;width:15px;height:20px;background:radial-gradient(12px 20px at 0 0,transparent 96%,var(--mc-ink) 100%)}
.mc-sq.me{align-self:flex-end;margin:0 12px 0 0;border-color:var(--mc-plum);border-radius:24px 24px 0 24px;background:var(--mc-plum);color:#fff}
.mc-sq.me::after{left:auto;right:-13.5px;background:radial-gradient(12px 20px at 100% 0,transparent 96%,var(--mc-plum) 100%)}
@supports (corner-shape:scoop){
  .mc-sq{corner-shape:squircle;border-radius:34px 34px 34px 0}
  .mc-sq.me{border-radius:34px 34px 0 34px}
  .mc-sq::after{background:var(--mc-card);border:solid var(--mc-ink);border-width:1.5px 0 1.5px 1.5px;border-top-left-radius:100% 100%;corner-shape:scoop}
  .mc-sq.me::after{background:var(--mc-plum);border-color:var(--mc-plum);border-width:1.5px 1.5px 1.5px 0;border-top-left-radius:0;border-top-right-radius:100% 100%}
}

/* 3. Anchor positioning: the checked seat becomes the anchor (anchor-scope keeps one name per seat), the bubble
   tries above, below and shifted placements, and an anchored container query moves the tail to match. */
.mc-cabin{position:relative;display:flex;flex-direction:column;align-items:center;gap:14px;margin:0;padding:24px 16px 30px;border:1px solid var(--mc-line);border-radius:20px;background:#fbf9f5;min-width:0}
.mc-h{margin:0 0 6px;text-align:center;font-weight:700;font-size:.95rem}
.mc-seats{display:grid;grid-template-columns:repeat(2,40px) 24px repeat(2,40px);gap:8px}
.mc-row{display:contents}
.mc-aisle{display:grid;place-items:center;font-size:.72rem;color:#5d574d}
.mc-seat{anchor-scope:--seat}
.mc-seat label{display:grid;place-items:center;width:40px;height:40px;border:1.5px solid #8c8476;border-radius:10px 10px 6px 6px;background:#fff;font-size:.78rem;font-weight:700;color:var(--mc-ink);cursor:pointer}
.mc-seat label:hover{border-color:var(--mc-teal)}
.mc-seat input:checked+label{anchor-name:--seat;border-color:var(--mc-teal);background:var(--mc-teal);color:#fff}
.mc-seat input:focus-visible+label{outline:2px solid var(--mc-teal);outline-offset:3px}
.mc-seat input:disabled+label{border-style:dashed;border-color:#b3ab9d;background:repeating-linear-gradient(-45deg,#ece6db 0 4px,#f7f3ec 4px 8px);color:#6e675b;cursor:not-allowed}
.mc-bub{display:none;position:absolute;z-index:2;position-anchor:--seat;position-area:top;justify-self:unsafe anchor-center;margin-bottom:12px;width:200px;container-type:anchored;
  position-try-fallbacks:--mc-b,--mc-tr,--mc-tl,--mc-br,--mc-bl}
.mc-seat input:checked~.mc-bub{display:block}
@position-try --mc-b{position-area:bottom;justify-self:unsafe anchor-center;margin:12px 0 0}
@position-try --mc-tr{position-area:top span-right;justify-self:normal;margin:0 0 12px}
@position-try --mc-tl{position-area:top span-left;justify-self:normal;margin:0 0 12px}
@position-try --mc-br{position-area:bottom span-right;justify-self:normal;margin:12px 0 0}
@position-try --mc-bl{position-area:bottom span-left;justify-self:normal;margin:12px 0 0}
.mc-bub-in{position:relative;display:block;padding:10px 13px;border-radius:12px;background:var(--mc-ink);color:#fff;font-size:.8rem;line-height:1.4;box-shadow:0 12px 26px -12px rgba(27,26,23,.6)}
.mc-bub-in b{display:block;font-size:.86rem}
.mc-bub-in::after{content:"";position:absolute;top:100%;left:50%;width:14px;height:8px;translate:-50% 0;background:inherit;clip-path:polygon(0 0,100% 0,50% 100%)}
@container anchored(fallback:--mc-b){.mc-bub-in::after{top:auto;bottom:100%;clip-path:polygon(50% 0,100% 100%,0 100%)}}
@container anchored(fallback:--mc-tr){.mc-bub-in::after{left:20px}}
@container anchored(fallback:--mc-tl){.mc-bub-in::after{left:auto;right:20px;translate:50% 0}}
@container anchored(fallback:--mc-br){.mc-bub-in::after{top:auto;bottom:100%;left:20px;clip-path:polygon(50% 0,100% 100%,0 100%)}}
@container anchored(fallback:--mc-bl){.mc-bub-in::after{top:auto;bottom:100%;left:auto;right:20px;translate:50% 0;clip-path:polygon(50% 0,100% 100%,0 100%)}}
/* Anchor positioning without anchored queries: no fallbacks, so the bubble stays above with its tail centred */
@supports not (container-type:anchored){.mc-bub{position-try-fallbacks:none}}
/* No anchor positioning: the bubble sits above its own seat */
@supports not (anchor-name:--seat){
  .mc-seat{position:relative}
  .mc-bub{bottom:100%;left:50%;translate:-50% 0;width:180px}
}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#5d574d] uppercase">Bubble and tail as one shape()</span>
<div class="flex flex-col gap-[18px] rounded-[20px] border border-mc-line bg-[#fbf9f5] px-[22px] py-[26px]">
  <div class="mc-shape drop-shadow-[0_8px_12px_rgba(27,26,23,.16)]"><p class="relative m-0 w-fit max-w-[88%] px-[18px] py-3.5 text-[.95rem] leading-[1.45] text-white bg-mc-teal"><span class="sr-only">Mira: </span>The ferry to Hvar leaves at 14:30. Shall I grab two tickets?</p></div>
  <div class="mc-shape mc-me self-end drop-shadow-[0_8px_12px_rgba(27,26,23,.16)]"><p class="relative m-0 w-fit max-w-[88%] px-[18px] py-3.5 text-[.95rem] leading-[1.45] text-white ml-auto bg-mc-plum"><span class="sr-only">You: </span>Yes please, and a table on the top deck if they still do those.</p></div>
</div>
<p class="mt-3.5 text-[.8rem] text-[#5d574d]">One <code class="rounded-[5px] bg-[#e9e2d6] px-[5px] py-px font-mono text-[.74rem] text-[#7a2d0c]">clip-path: shape()</code> per bubble draws corners and curved tail together.</p></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#5d574d] uppercase">Squircle bubbles with scooped tails</span>
<div class="flex flex-col gap-[18px] rounded-[20px] border border-mc-line bg-[#fbf9f5] px-[22px] py-[26px]">
  <p class="mc-sq relative m-0 w-fit max-w-[86%] border-[1.5px] px-[18px] py-3.5 text-[.95rem] leading-[1.45] ml-3 rounded-[24px_24px_24px_0] border-mc-ink bg-white text-mc-ink supports-[corner-shape:scoop]:rounded-[34px_34px_34px_0] supports-[corner-shape:scoop]:[corner-shape:squircle]"><span class="sr-only">Mira: </span>Top deck is first come, first served. We'd need to board by 14:00.</p>
  <p class="mc-sq relative m-0 w-fit max-w-[86%] border-[1.5px] px-[18px] py-3.5 text-[.95rem] leading-[1.45] mc-me mr-3 self-end rounded-[24px_24px_0_24px] border-mc-plum bg-mc-plum text-white supports-[corner-shape:scoop]:rounded-[34px_34px_0_34px] supports-[corner-shape:scoop]:[corner-shape:squircle]"><span class="sr-only">You: </span>Then let's meet at the kiosk on the pier at 13:45.</p>
</div>
<p class="mt-3.5 text-[.8rem] text-[#5d574d]"><code class="rounded-[5px] bg-[#e9e2d6] px-[5px] py-px font-mono text-[.74rem] text-[#7a2d0c]">corner-shape: squircle</code> on the bubble, <code class="rounded-[5px] bg-[#e9e2d6] px-[5px] py-px font-mono text-[.74rem] text-[#7a2d0c]">corner-shape: scoop</code> on the tail.</p></div>

<div class="col-span-full"><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#5d574d] uppercase">Anchored bubble that keeps its tail on the anchor</span>
<div class="relative flex min-w-0 flex-col items-center gap-3.5 rounded-[20px] border border-mc-line bg-[#fbf9f5] px-4 pt-6 pb-[30px]" role="radiogroup" aria-labelledby="mc-cabin-h">
  <p class="mb-1.5 text-center text-[.95rem] font-bold" id="mc-cabin-h">Pick a seat, rows 1 to 4</p>
  <div class="grid grid-cols-[repeat(2,40px)_24px_repeat(2,40px)] gap-2">
    <div class="contents">
      <span class="mc-seat [anchor-scope:--seat]"><input class="peer absolute size-px opacity-0" type="radio" name="mc-seat" id="mc-1a" checked><label class="grid size-10 cursor-pointer place-items-center rounded-[10px_10px_6px_6px] border-[1.5px] border-[#8c8476] bg-white text-[.78rem] font-bold text-mc-ink hover:border-mc-teal peer-checked:border-mc-teal peer-checked:bg-mc-teal peer-checked:text-white peer-checked:[anchor-name:--seat] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-mc-teal peer-disabled:cursor-not-allowed peer-disabled:border-dashed peer-disabled:border-[#b3ab9d] peer-disabled:bg-[repeating-linear-gradient(-45deg,#ece6db_0_4px,#f7f3ec_4px_8px)] peer-disabled:text-[#6e675b]" for="mc-1a">1A<span class="sr-only">, window, extra legroom, €18</span></label><span class="mc-bub hidden peer-checked:block" aria-hidden="true"><span class="mc-bub-in relative block rounded-xl bg-mc-ink px-[13px] py-2.5 text-[.8rem] leading-[1.4] text-white shadow-[0_12px_26px_-12px_rgba(27,26,23,.6)]"><b class="block text-[.86rem]">1A · Window</b>Extra legroom, €18</span></span></span>
      <span class="mc-seat [anchor-scope:--seat]"><input class="peer absolute size-px opacity-0" type="radio" name="mc-seat" id="mc-1b" disabled><label class="grid size-10 cursor-pointer place-items-center rounded-[10px_10px_6px_6px] border-[1.5px] border-[#8c8476] bg-white text-[.78rem] font-bold text-mc-ink hover:border-mc-teal peer-checked:border-mc-teal peer-checked:bg-mc-teal peer-checked:text-white peer-checked:[anchor-name:--seat] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-mc-teal peer-disabled:cursor-not-allowed peer-disabled:border-dashed peer-disabled:border-[#b3ab9d] peer-disabled:bg-[repeating-linear-gradient(-45deg,#ece6db_0_4px,#f7f3ec_4px_8px)] peer-disabled:text-[#6e675b]" for="mc-1b">1B<span class="sr-only">, taken</span></label></span>
      <span class="grid place-items-center text-[.72rem] text-[#5d574d]" aria-hidden="true">1</span>
      <span class="mc-seat [anchor-scope:--seat]"><input class="peer absolute size-px opacity-0" type="radio" name="mc-seat" id="mc-1c" disabled><label class="grid size-10 cursor-pointer place-items-center rounded-[10px_10px_6px_6px] border-[1.5px] border-[#8c8476] bg-white text-[.78rem] font-bold text-mc-ink hover:border-mc-teal peer-checked:border-mc-teal peer-checked:bg-mc-teal peer-checked:text-white peer-checked:[anchor-name:--seat] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-mc-teal peer-disabled:cursor-not-allowed peer-disabled:border-dashed peer-disabled:border-[#b3ab9d] peer-disabled:bg-[repeating-linear-gradient(-45deg,#ece6db_0_4px,#f7f3ec_4px_8px)] peer-disabled:text-[#6e675b]" for="mc-1c">1C<span class="sr-only">, taken</span></label></span>
      <span class="mc-seat [anchor-scope:--seat]"><input class="peer absolute size-px opacity-0" type="radio" name="mc-seat" id="mc-1d"><label class="grid size-10 cursor-pointer place-items-center rounded-[10px_10px_6px_6px] border-[1.5px] border-[#8c8476] bg-white text-[.78rem] font-bold text-mc-ink hover:border-mc-teal peer-checked:border-mc-teal peer-checked:bg-mc-teal peer-checked:text-white peer-checked:[anchor-name:--seat] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-mc-teal peer-disabled:cursor-not-allowed peer-disabled:border-dashed peer-disabled:border-[#b3ab9d] peer-disabled:bg-[repeating-linear-gradient(-45deg,#ece6db_0_4px,#f7f3ec_4px_8px)] peer-disabled:text-[#6e675b]" for="mc-1d">1D<span class="sr-only">, window, extra legroom, €18</span></label><span class="mc-bub hidden peer-checked:block" aria-hidden="true"><span class="mc-bub-in relative block rounded-xl bg-mc-ink px-[13px] py-2.5 text-[.8rem] leading-[1.4] text-white shadow-[0_12px_26px_-12px_rgba(27,26,23,.6)]"><b class="block text-[.86rem]">1D · Window</b>Extra legroom, €18</span></span></span>
    </div>
    <div class="contents">
      <span class="mc-seat [anchor-scope:--seat]"><input class="peer absolute size-px opacity-0" type="radio" name="mc-seat" id="mc-2a" disabled><label class="grid size-10 cursor-pointer place-items-center rounded-[10px_10px_6px_6px] border-[1.5px] border-[#8c8476] bg-white text-[.78rem] font-bold text-mc-ink hover:border-mc-teal peer-checked:border-mc-teal peer-checked:bg-mc-teal peer-checked:text-white peer-checked:[anchor-name:--seat] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-mc-teal peer-disabled:cursor-not-allowed peer-disabled:border-dashed peer-disabled:border-[#b3ab9d] peer-disabled:bg-[repeating-linear-gradient(-45deg,#ece6db_0_4px,#f7f3ec_4px_8px)] peer-disabled:text-[#6e675b]" for="mc-2a">2A<span class="sr-only">, taken</span></label></span>
      <span class="mc-seat [anchor-scope:--seat]"><input class="peer absolute size-px opacity-0" type="radio" name="mc-seat" id="mc-2b"><label class="grid size-10 cursor-pointer place-items-center rounded-[10px_10px_6px_6px] border-[1.5px] border-[#8c8476] bg-white text-[.78rem] font-bold text-mc-ink hover:border-mc-teal peer-checked:border-mc-teal peer-checked:bg-mc-teal peer-checked:text-white peer-checked:[anchor-name:--seat] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-mc-teal peer-disabled:cursor-not-allowed peer-disabled:border-dashed peer-disabled:border-[#b3ab9d] peer-disabled:bg-[repeating-linear-gradient(-45deg,#ece6db_0_4px,#f7f3ec_4px_8px)] peer-disabled:text-[#6e675b]" for="mc-2b">2B<span class="sr-only">, aisle, €9</span></label><span class="mc-bub hidden peer-checked:block" aria-hidden="true"><span class="mc-bub-in relative block rounded-xl bg-mc-ink px-[13px] py-2.5 text-[.8rem] leading-[1.4] text-white shadow-[0_12px_26px_-12px_rgba(27,26,23,.6)]"><b class="block text-[.86rem]">2B · Aisle</b>Standard seat, €9</span></span></span>
      <span class="grid place-items-center text-[.72rem] text-[#5d574d]" aria-hidden="true">2</span>
      <span class="mc-seat [anchor-scope:--seat]"><input class="peer absolute size-px opacity-0" type="radio" name="mc-seat" id="mc-2c" disabled><label class="grid size-10 cursor-pointer place-items-center rounded-[10px_10px_6px_6px] border-[1.5px] border-[#8c8476] bg-white text-[.78rem] font-bold text-mc-ink hover:border-mc-teal peer-checked:border-mc-teal peer-checked:bg-mc-teal peer-checked:text-white peer-checked:[anchor-name:--seat] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-mc-teal peer-disabled:cursor-not-allowed peer-disabled:border-dashed peer-disabled:border-[#b3ab9d] peer-disabled:bg-[repeating-linear-gradient(-45deg,#ece6db_0_4px,#f7f3ec_4px_8px)] peer-disabled:text-[#6e675b]" for="mc-2c">2C<span class="sr-only">, taken</span></label></span>
      <span class="mc-seat [anchor-scope:--seat]"><input class="peer absolute size-px opacity-0" type="radio" name="mc-seat" id="mc-2d" disabled><label class="grid size-10 cursor-pointer place-items-center rounded-[10px_10px_6px_6px] border-[1.5px] border-[#8c8476] bg-white text-[.78rem] font-bold text-mc-ink hover:border-mc-teal peer-checked:border-mc-teal peer-checked:bg-mc-teal peer-checked:text-white peer-checked:[anchor-name:--seat] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-mc-teal peer-disabled:cursor-not-allowed peer-disabled:border-dashed peer-disabled:border-[#b3ab9d] peer-disabled:bg-[repeating-linear-gradient(-45deg,#ece6db_0_4px,#f7f3ec_4px_8px)] peer-disabled:text-[#6e675b]" for="mc-2d">2D<span class="sr-only">, taken</span></label></span>
    </div>
    <div class="contents">
      <span class="mc-seat [anchor-scope:--seat]"><input class="peer absolute size-px opacity-0" type="radio" name="mc-seat" id="mc-3a"><label class="grid size-10 cursor-pointer place-items-center rounded-[10px_10px_6px_6px] border-[1.5px] border-[#8c8476] bg-white text-[.78rem] font-bold text-mc-ink hover:border-mc-teal peer-checked:border-mc-teal peer-checked:bg-mc-teal peer-checked:text-white peer-checked:[anchor-name:--seat] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-mc-teal peer-disabled:cursor-not-allowed peer-disabled:border-dashed peer-disabled:border-[#b3ab9d] peer-disabled:bg-[repeating-linear-gradient(-45deg,#ece6db_0_4px,#f7f3ec_4px_8px)] peer-disabled:text-[#6e675b]" for="mc-3a">3A<span class="sr-only">, window, €9</span></label><span class="mc-bub hidden peer-checked:block" aria-hidden="true"><span class="mc-bub-in relative block rounded-xl bg-mc-ink px-[13px] py-2.5 text-[.8rem] leading-[1.4] text-white shadow-[0_12px_26px_-12px_rgba(27,26,23,.6)]"><b class="block text-[.86rem]">3A · Window</b>Standard seat, €9</span></span></span>
      <span class="mc-seat [anchor-scope:--seat]"><input class="peer absolute size-px opacity-0" type="radio" name="mc-seat" id="mc-3b" disabled><label class="grid size-10 cursor-pointer place-items-center rounded-[10px_10px_6px_6px] border-[1.5px] border-[#8c8476] bg-white text-[.78rem] font-bold text-mc-ink hover:border-mc-teal peer-checked:border-mc-teal peer-checked:bg-mc-teal peer-checked:text-white peer-checked:[anchor-name:--seat] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-mc-teal peer-disabled:cursor-not-allowed peer-disabled:border-dashed peer-disabled:border-[#b3ab9d] peer-disabled:bg-[repeating-linear-gradient(-45deg,#ece6db_0_4px,#f7f3ec_4px_8px)] peer-disabled:text-[#6e675b]" for="mc-3b">3B<span class="sr-only">, taken</span></label></span>
      <span class="grid place-items-center text-[.72rem] text-[#5d574d]" aria-hidden="true">3</span>
      <span class="mc-seat [anchor-scope:--seat]"><input class="peer absolute size-px opacity-0" type="radio" name="mc-seat" id="mc-3c"><label class="grid size-10 cursor-pointer place-items-center rounded-[10px_10px_6px_6px] border-[1.5px] border-[#8c8476] bg-white text-[.78rem] font-bold text-mc-ink hover:border-mc-teal peer-checked:border-mc-teal peer-checked:bg-mc-teal peer-checked:text-white peer-checked:[anchor-name:--seat] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-mc-teal peer-disabled:cursor-not-allowed peer-disabled:border-dashed peer-disabled:border-[#b3ab9d] peer-disabled:bg-[repeating-linear-gradient(-45deg,#ece6db_0_4px,#f7f3ec_4px_8px)] peer-disabled:text-[#6e675b]" for="mc-3c">3C<span class="sr-only">, aisle, €9</span></label><span class="mc-bub hidden peer-checked:block" aria-hidden="true"><span class="mc-bub-in relative block rounded-xl bg-mc-ink px-[13px] py-2.5 text-[.8rem] leading-[1.4] text-white shadow-[0_12px_26px_-12px_rgba(27,26,23,.6)]"><b class="block text-[.86rem]">3C · Aisle</b>Standard seat, €9</span></span></span>
      <span class="mc-seat [anchor-scope:--seat]"><input class="peer absolute size-px opacity-0" type="radio" name="mc-seat" id="mc-3d" disabled><label class="grid size-10 cursor-pointer place-items-center rounded-[10px_10px_6px_6px] border-[1.5px] border-[#8c8476] bg-white text-[.78rem] font-bold text-mc-ink hover:border-mc-teal peer-checked:border-mc-teal peer-checked:bg-mc-teal peer-checked:text-white peer-checked:[anchor-name:--seat] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-mc-teal peer-disabled:cursor-not-allowed peer-disabled:border-dashed peer-disabled:border-[#b3ab9d] peer-disabled:bg-[repeating-linear-gradient(-45deg,#ece6db_0_4px,#f7f3ec_4px_8px)] peer-disabled:text-[#6e675b]" for="mc-3d">3D<span class="sr-only">, taken</span></label></span>
    </div>
    <div class="contents">
      <span class="mc-seat [anchor-scope:--seat]"><input class="peer absolute size-px opacity-0" type="radio" name="mc-seat" id="mc-4a" disabled><label class="grid size-10 cursor-pointer place-items-center rounded-[10px_10px_6px_6px] border-[1.5px] border-[#8c8476] bg-white text-[.78rem] font-bold text-mc-ink hover:border-mc-teal peer-checked:border-mc-teal peer-checked:bg-mc-teal peer-checked:text-white peer-checked:[anchor-name:--seat] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-mc-teal peer-disabled:cursor-not-allowed peer-disabled:border-dashed peer-disabled:border-[#b3ab9d] peer-disabled:bg-[repeating-linear-gradient(-45deg,#ece6db_0_4px,#f7f3ec_4px_8px)] peer-disabled:text-[#6e675b]" for="mc-4a">4A<span class="sr-only">, taken</span></label></span>
      <span class="mc-seat [anchor-scope:--seat]"><input class="peer absolute size-px opacity-0" type="radio" name="mc-seat" id="mc-4b" disabled><label class="grid size-10 cursor-pointer place-items-center rounded-[10px_10px_6px_6px] border-[1.5px] border-[#8c8476] bg-white text-[.78rem] font-bold text-mc-ink hover:border-mc-teal peer-checked:border-mc-teal peer-checked:bg-mc-teal peer-checked:text-white peer-checked:[anchor-name:--seat] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-mc-teal peer-disabled:cursor-not-allowed peer-disabled:border-dashed peer-disabled:border-[#b3ab9d] peer-disabled:bg-[repeating-linear-gradient(-45deg,#ece6db_0_4px,#f7f3ec_4px_8px)] peer-disabled:text-[#6e675b]" for="mc-4b">4B<span class="sr-only">, taken</span></label></span>
      <span class="grid place-items-center text-[.72rem] text-[#5d574d]" aria-hidden="true">4</span>
      <span class="mc-seat [anchor-scope:--seat]"><input class="peer absolute size-px opacity-0" type="radio" name="mc-seat" id="mc-4c" disabled><label class="grid size-10 cursor-pointer place-items-center rounded-[10px_10px_6px_6px] border-[1.5px] border-[#8c8476] bg-white text-[.78rem] font-bold text-mc-ink hover:border-mc-teal peer-checked:border-mc-teal peer-checked:bg-mc-teal peer-checked:text-white peer-checked:[anchor-name:--seat] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-mc-teal peer-disabled:cursor-not-allowed peer-disabled:border-dashed peer-disabled:border-[#b3ab9d] peer-disabled:bg-[repeating-linear-gradient(-45deg,#ece6db_0_4px,#f7f3ec_4px_8px)] peer-disabled:text-[#6e675b]" for="mc-4c">4C<span class="sr-only">, taken</span></label></span>
      <span class="mc-seat [anchor-scope:--seat]"><input class="peer absolute size-px opacity-0" type="radio" name="mc-seat" id="mc-4d"><label class="grid size-10 cursor-pointer place-items-center rounded-[10px_10px_6px_6px] border-[1.5px] border-[#8c8476] bg-white text-[.78rem] font-bold text-mc-ink hover:border-mc-teal peer-checked:border-mc-teal peer-checked:bg-mc-teal peer-checked:text-white peer-checked:[anchor-name:--seat] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-mc-teal peer-disabled:cursor-not-allowed peer-disabled:border-dashed peer-disabled:border-[#b3ab9d] peer-disabled:bg-[repeating-linear-gradient(-45deg,#ece6db_0_4px,#f7f3ec_4px_8px)] peer-disabled:text-[#6e675b]" for="mc-4d">4D<span class="sr-only">, window, €9</span></label><span class="mc-bub hidden peer-checked:block" aria-hidden="true"><span class="mc-bub-in relative block rounded-xl bg-mc-ink px-[13px] py-2.5 text-[.8rem] leading-[1.4] text-white shadow-[0_12px_26px_-12px_rgba(27,26,23,.6)]"><b class="block text-[.86rem]">4D · Window</b>Standard seat, €9</span></span></span>
    </div>
  </div>
</div>
<p class="mt-3.5 text-[.8rem] text-[#5d574d]">Pick 1A, then 4D: the bubble flips below or shifts sideways when it runs out of room, and <code class="rounded-[5px] bg-[#e9e2d6] px-[5px] py-px font-mono text-[.74rem] text-[#7a2d0c]">@container anchored(fallback: …)</code> moves the tail with it.</p></div>
CSS
@theme {
  --color-mc-ink:#1b1a17; --color-mc-teal:#0e7468; --color-mc-plum:#5b2a86; --color-mc-line:#d9d1c4;
}
/* Utilities draw the bubbles and seats (peer-checked:[anchor-name:--seat], supports-[corner-shape:scoop]:…).
   The shape() paths, tails, @position-try rules and anchored container queries live here. */

/* 1. shape(): fallback is rounded corners plus a border-triangle tail */
.mc-shape p{margin-bottom:16px;border-radius:18px}
.mc-shape p::after{content:"";position:absolute;top:100%;left:22px;border:solid transparent;border-width:14px 14px 0 0;border-top-color:var(--color-mc-teal)}
.mc-shape.mc-me p::after{left:auto;right:22px;border-width:14px 0 0 14px;border-top-color:var(--color-mc-plum)}
@supports (clip-path:shape(from 0 0,line to 1px 1px)){
  .mc-shape p{margin-bottom:0;border-radius:0;padding-bottom:32px;
    clip-path:shape(from 18px 0,hline to calc(100% - 18px),curve to 100% 18px with 100% 0,vline to calc(100% - 36px),
      curve to calc(100% - 18px) calc(100% - 18px) with 100% calc(100% - 18px),hline to 60px,
      curve to 10px 100% with 34px calc(100% - 4px),curve to 26px calc(100% - 18px) with 24px calc(100% - 8px),
      hline to 18px,curve to 0 calc(100% - 36px) with 0 calc(100% - 18px),vline to 18px,curve to 18px 0 with 0 0,close)}
  .mc-shape.mc-me p{clip-path:shape(from calc(100% - 18px) 0,hline to 18px,curve to 0 18px with 0 0,vline to calc(100% - 36px),
      curve to 18px calc(100% - 18px) with 0 calc(100% - 18px),hline to calc(100% - 60px),
      curve to calc(100% - 10px) 100% with calc(100% - 34px) calc(100% - 4px),curve to calc(100% - 26px) calc(100% - 18px) with calc(100% - 24px) calc(100% - 8px),
      hline to calc(100% - 18px),curve to 100% calc(100% - 36px) with 100% calc(100% - 18px),vline to 18px,curve to calc(100% - 18px) 0 with 100% 0,close)}
  .mc-shape p::after{display:none}
}


/* 2. Scooped tail: radial-gradient fallback, corner-shape: scoop where supported */
.mc-sq::after{content:"";position:absolute;bottom:-1.5px;left:-13.5px;width:15px;height:20px;background:radial-gradient(12px 20px at 0 0,transparent 96%,var(--color-mc-ink) 100%)}
.mc-sq.mc-me::after{left:auto;right:-13.5px;background:radial-gradient(12px 20px at 100% 0,transparent 96%,var(--color-mc-plum) 100%)}
@supports (corner-shape:scoop){
  .mc-sq::after{background:#fff;border:solid var(--color-mc-ink);border-width:1.5px 0 1.5px 1.5px;border-top-left-radius:100% 100%;corner-shape:scoop}
  .mc-sq.mc-me::after{background:var(--color-mc-plum);border-color:var(--color-mc-plum);border-width:1.5px 1.5px 1.5px 0;border-top-left-radius:0;border-top-right-radius:100% 100%}
}

/* 3. Anchored bubble */
.mc-bub{position:absolute;z-index:2;position-anchor:--seat;position-area:top;justify-self:unsafe anchor-center;margin-bottom:12px;width:200px;container-type:anchored;
  position-try-fallbacks:--mc-b,--mc-tr,--mc-tl,--mc-br,--mc-bl}
@position-try --mc-b{position-area:bottom;justify-self:unsafe anchor-center;margin:12px 0 0}
@position-try --mc-tr{position-area:top span-right;justify-self:normal;margin:0 0 12px}
@position-try --mc-tl{position-area:top span-left;justify-self:normal;margin:0 0 12px}
@position-try --mc-br{position-area:bottom span-right;justify-self:normal;margin:12px 0 0}
@position-try --mc-bl{position-area:bottom span-left;justify-self:normal;margin:12px 0 0}
.mc-bub-in::after{content:"";position:absolute;top:100%;left:50%;width:14px;height:8px;translate:-50% 0;background:inherit;clip-path:polygon(0 0,100% 0,50% 100%)}
@container anchored(fallback:--mc-b){.mc-bub-in::after{top:auto;bottom:100%;clip-path:polygon(50% 0,100% 100%,0 100%)}}
@container anchored(fallback:--mc-tr){.mc-bub-in::after{left:20px}}
@container anchored(fallback:--mc-tl){.mc-bub-in::after{left:auto;right:20px;translate:50% 0}}
@container anchored(fallback:--mc-br){.mc-bub-in::after{top:auto;bottom:100%;left:20px;clip-path:polygon(50% 0,100% 100%,0 100%)}}
@container anchored(fallback:--mc-bl){.mc-bub-in::after{top:auto;bottom:100%;left:auto;right:20px;translate:50% 0;clip-path:polygon(50% 0,100% 100%,0 100%)}}
@supports not (container-type:anchored){.mc-bub{position-try-fallbacks:none}}
@supports not (anchor-name:--seat){.mc-seat{position:relative}.mc-bub{bottom:100%;left:50%;translate:-50% 0;width:180px}}

About this set

Three bubbles built with CSS features from 2024 and 2025, each behind @supports with a classic bubble as the fallback. Bubble and tail as one shape() draws the corners and a curved, tapering tail as a single clip-path: shape() path written with calc() and percentages, so it stretches with the text; the shadow comes from drop-shadow on a wrapper because clip-path would cut it off. Squircle bubbles with scooped tails uses corner-shape: squircle for the bubble corners and a tail that is one corner of a small box with corner-shape: scoop, so even the outlined bubble’s border follows the tail’s concave curve. Anchored bubble that keeps its tail on the anchor is a seat picker built from radio buttons: the checked seat becomes the anchor, anchor-scope lets every seat reuse the name –seat, and the bubble tries above, below and shifted placements with position-try-fallbacks. Anchored container queries then move the tail to match the placement, so it always points at the seat. It needs no JavaScript.

What’s included

  • Bubble and tail drawn as one clip-path: shape() path
  • corner-shape: squircle bubbles with corner-shape: scoop tails
  • Anchor-positioned bubble with five @position-try placements
  • Anchored container queries keep the tail on the anchor
  • Classic fallbacks behind @supports for every technique

Accessibility

Messages start with visually hidden sender names. The seat picker is a radiogroup labelled by its heading, each seat is a native radio with a label that includes its details or the word taken, taken seats are disabled, and the focused seat shows a 2px outline. The visual bubble repeats the label text, so it is aria-hidden.

Customise it

Change --mc-teal, --mc-plum and --mc-ink on .mc for colours. Edit the 18px corner and 60px / 10px tail points in the shape() paths, the corner radii on .mc-sq, and the @position-try rules to change where the seat bubble may go. In Tailwind edit the --color-mc-* theme colours; Bootstrap uses .card stages and .btn-check radios.