21 / 21
All 21 in 21 Tailwind CSS Tooltips
- 01Tailwind Schematic Hotspot Tooltip with Pulsing CAD Callouts
- 02Tailwind Keyboard Shortcut Tooltip with Animated 3D Keycaps
- 03Tailwind IDE Function Signature Tooltip with Typed Docs Popover
- 04Tailwind Editorial Footnote Tooltip with Torn-Paper Sidenote
- 05Tailwind Profile Hover Card Tooltip with Staggered Reveal
- 06Tailwind RPG Inventory Tooltip with Rarity-Tinted Item Cards
- 07Tailwind Glossary Word Tooltip with Dictionary Definition Card
- 08Tailwind Service Status Tooltip with 30-Day Uptime History
- 09Tailwind Team Avatar Hover Card with Presence and Quick Actions
- 10Tailwind Keyboard Shortcut Reference Tooltip with When-To-Use Card
- 11Tailwind Data Table Cell Tooltip with Segment Breakdown Bars
- 12Tailwind Content Tag Tooltip with Topic Detail and Popularity Bar
- 13Tailwind Airport Departures Board Tooltip with Flight Detail Card
- 14Tailwind Recipe Ingredient Tooltip with Nutrition and Substitutions
- 15Tailwind Constellation Star Chart Tooltip with Astronomical Data
- 16Tailwind Periodic Table Element Tooltip with Category Colour Coding
- 17Tailwind Photo Grid EXIF Tooltip with Camera Settings Card
- 18Tailwind Org Chart Node Tooltip with Reporting-Line Detail Card
- 19Tailwind Company Timeline Tooltip with Alternating Event Cards
- 20Tailwind Icon Toolbar Tooltip with Pure-CSS Arrow Pill
- 21Tailwind Form Field Help Tooltip with Side-Anchored Hint Pill
Tailwind Form Field Help Tooltip with Side-Anchored Hint Pill
Small help circles beside form labels that reveal a one-line rule to the right, so the hint never covers the field above. The port's correction is semantic: the hint is wired to the INPUT with aria-describedby, which means a screen reader reads the rule while typing — not only when something is hovered.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="ttp-21 w-full min-h-svh bg-white py-10 pr-[320px] pl-7 font-ttp21-sans max-xl:pr-7">
<form class="mx-auto max-w-[400px]">
<div class="mb-4.5">
<label for="ttp21-f1" class="ttp21-label">Username
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the username field" aria-describedby="ttp21-h1" class="ttp21-icon">?</button>
<span id="ttp21-h1" role="tooltip" class="ttp21-tip">Letters, numbers and underscores only. 3–20 characters.</span>
</span>
</label>
<input id="ttp21-f1" aria-describedby="ttp21-h1" class="ttp21-input" type="text" value="kalani_a" readonly>
</div>
<div class="mb-4.5">
<label for="ttp21-f2" class="ttp21-label">Email address
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the email field" aria-describedby="ttp21-h2" class="ttp21-icon">?</button>
<span id="ttp21-h2" role="tooltip" class="ttp21-tip">Used for login and account recovery. Never shown publicly.</span>
</span>
</label>
<input id="ttp21-f2" aria-describedby="ttp21-h2" class="ttp21-input" type="email" value="kalani@example.com" readonly>
</div>
<div class="mb-4.5">
<label for="ttp21-f3" class="ttp21-label">Website
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the website field" aria-describedby="ttp21-h3" class="ttp21-icon">?</button>
<span id="ttp21-h3" role="tooltip" class="ttp21-tip">Include https:// — displayed on your public profile page.</span>
</span>
</label>
<input id="ttp21-f3" aria-describedby="ttp21-h3" class="ttp21-input" type="text" value="https://kalani.dev" readonly>
</div>
<div class="mb-4.5">
<label for="ttp21-f4" class="ttp21-label">Bio
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the bio field" aria-describedby="ttp21-h4" class="ttp21-icon">?</button>
<span id="ttp21-h4" role="tooltip" class="ttp21-tip">Shown below your name on your profile. Max 160 characters.</span>
</span>
</label>
<input id="ttp21-f4" aria-describedby="ttp21-h4" class="ttp21-input" type="text" value="Building things on the web. Based in Honolulu." readonly>
</div>
<div>
<label for="ttp21-f5" class="ttp21-label">Time zone
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the time zone field" aria-describedby="ttp21-h5" class="ttp21-icon">?</button>
<span id="ttp21-h5" role="tooltip" class="ttp21-tip">Used to display timestamps and schedule notifications correctly.</span>
</span>
</label>
<input id="ttp21-f5" aria-describedby="ttp21-h5" class="ttp21-input" type="text" value="Pacific/Honolulu (UTC −10:00)" readonly>
</div>
</form>
</section><section class="ttp-21 w-full min-h-svh bg-white py-10 pr-[320px] pl-7 font-ttp21-sans max-xl:pr-7">
<form class="mx-auto max-w-[400px]">
<div class="mb-4.5">
<label for="ttp21-f1" class="ttp21-label">Username
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the username field" aria-describedby="ttp21-h1" class="ttp21-icon">?</button>
<span id="ttp21-h1" role="tooltip" class="ttp21-tip">Letters, numbers and underscores only. 3–20 characters.</span>
</span>
</label>
<input id="ttp21-f1" aria-describedby="ttp21-h1" class="ttp21-input" type="text" value="kalani_a" readonly>
</div>
<div class="mb-4.5">
<label for="ttp21-f2" class="ttp21-label">Email address
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the email field" aria-describedby="ttp21-h2" class="ttp21-icon">?</button>
<span id="ttp21-h2" role="tooltip" class="ttp21-tip">Used for login and account recovery. Never shown publicly.</span>
</span>
</label>
<input id="ttp21-f2" aria-describedby="ttp21-h2" class="ttp21-input" type="email" value="kalani@example.com" readonly>
</div>
<div class="mb-4.5">
<label for="ttp21-f3" class="ttp21-label">Website
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the website field" aria-describedby="ttp21-h3" class="ttp21-icon">?</button>
<span id="ttp21-h3" role="tooltip" class="ttp21-tip">Include https:// — displayed on your public profile page.</span>
</span>
</label>
<input id="ttp21-f3" aria-describedby="ttp21-h3" class="ttp21-input" type="text" value="https://kalani.dev" readonly>
</div>
<div class="mb-4.5">
<label for="ttp21-f4" class="ttp21-label">Bio
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the bio field" aria-describedby="ttp21-h4" class="ttp21-icon">?</button>
<span id="ttp21-h4" role="tooltip" class="ttp21-tip">Shown below your name on your profile. Max 160 characters.</span>
</span>
</label>
<input id="ttp21-f4" aria-describedby="ttp21-h4" class="ttp21-input" type="text" value="Building things on the web. Based in Honolulu." readonly>
</div>
<div>
<label for="ttp21-f5" class="ttp21-label">Time zone
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the time zone field" aria-describedby="ttp21-h5" class="ttp21-icon">?</button>
<span id="ttp21-h5" role="tooltip" class="ttp21-tip">Used to display timestamps and schedule notifications correctly.</span>
</span>
</label>
<input id="ttp21-f5" aria-describedby="ttp21-h5" class="ttp21-input" type="text" value="Pacific/Honolulu (UTC −10:00)" readonly>
</div>
</form>
</section>@import "tailwindcss";
@theme {
--color-ttp21-ink: #111111;
--color-ttp21-ink: oklch(0.18 0 0);
--color-ttp21-field: #374151;
--color-ttp21-field: oklch(0.37 0.03 260);
--color-ttp21-icon: #6b7280;
--color-ttp21-icon: oklch(0.55 0.02 260);
--color-ttp21-chip: #e5e7eb;
--color-ttp21-chip: oklch(0.92 0.005 260);
--color-ttp21-chiphover: #cbd0d8;
--color-ttp21-chiphover: oklch(0.85 0.008 260);
--color-ttp21-pill: #18181b;
--color-ttp21-pill: oklch(0.21 0.005 285);
--color-ttp21-focus: #4f46e5;
--color-ttp21-focus: oklch(0.51 0.21 277);
--font-ttp21-sans: system-ui, -apple-system, sans-serif;
}
.ttp-21 {
width: 100%;
min-height: 100svh;
}
/* Five fields, one help affordance. */
.ttp-21 .ttp21-label {
@apply mb-1.5 flex items-center gap-1.25 text-[12.5px] font-medium text-ttp21-ink;
}
.ttp-21 .ttp21-icon {
@apply inline-flex h-4 w-4 shrink-0 cursor-help items-center justify-center rounded-full border-0 bg-ttp21-chip p-0 text-[9.5px] font-bold leading-none text-ttp21-icon transition-colors duration-100 hover:bg-ttp21-chiphover hover:text-ttp21-field focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ttp21-focus;
}
.ttp-21 .ttp21-tip {
@apply pointer-events-none invisible absolute top-1/2 left-[calc(100%+8px)] z-100 block w-60 max-w-[calc(100vw-48px)] -translate-y-1/2 translate-x-1 rounded-[5px] bg-ttp21-pill px-2.75 py-1.75 text-[11.5px] leading-[1.4] text-white opacity-0 transition-[opacity,translate,visibility] duration-150 ease-[cubic-bezier(0.22,1,0.36,1)] after:absolute after:top-1/2 after:right-full after:-translate-y-1/2 after:border-4 after:border-transparent after:border-r-ttp21-pill after:content-[''] group-hover:visible group-hover:translate-x-0 group-hover:opacity-100 group-focus-within:visible group-focus-within:translate-x-0 group-focus-within:opacity-100;
}
.ttp-21 .ttp21-input {
@apply w-full rounded-md border border-ttp21-chip bg-[oklch(0.98_0_0)] px-2.75 py-2 text-[12.5px] text-ttp21-field outline-none focus:border-ttp21-focus focus:bg-white;
}
/* Under 640px there is no room to the side: the pill anchors above
the label and the arrow moves to the bottom edge. */
@media (max-width: 639px) {
.ttp-21 .ttp21-tip {
@apply bottom-[calc(100%+8px)] top-auto left-0 w-[calc(100vw-40px)] max-w-[280px] translate-x-0 translate-y-1 group-hover:translate-y-0 group-focus-within:translate-y-0 after:top-full after:right-auto after:left-3 after:translate-y-0 after:border-r-transparent after:border-t-ttp21-pill;
}
}@import "tailwindcss";
@theme {
--color-ttp21-ink: #111111;
--color-ttp21-ink: oklch(0.18 0 0);
--color-ttp21-field: #374151;
--color-ttp21-field: oklch(0.37 0.03 260);
--color-ttp21-icon: #6b7280;
--color-ttp21-icon: oklch(0.55 0.02 260);
--color-ttp21-chip: #e5e7eb;
--color-ttp21-chip: oklch(0.92 0.005 260);
--color-ttp21-chiphover: #cbd0d8;
--color-ttp21-chiphover: oklch(0.85 0.008 260);
--color-ttp21-pill: #18181b;
--color-ttp21-pill: oklch(0.21 0.005 285);
--color-ttp21-focus: #4f46e5;
--color-ttp21-focus: oklch(0.51 0.21 277);
--font-ttp21-sans: system-ui, -apple-system, sans-serif;
}
.ttp-21 {
width: 100%;
min-height: 100svh;
}
/* Five fields, one help affordance. */
.ttp-21 .ttp21-label {
@apply mb-1.5 flex items-center gap-1.25 text-[12.5px] font-medium text-ttp21-ink;
}
.ttp-21 .ttp21-icon {
@apply inline-flex h-4 w-4 shrink-0 cursor-help items-center justify-center rounded-full border-0 bg-ttp21-chip p-0 text-[9.5px] font-bold leading-none text-ttp21-icon transition-colors duration-100 hover:bg-ttp21-chiphover hover:text-ttp21-field focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ttp21-focus;
}
.ttp-21 .ttp21-tip {
@apply pointer-events-none invisible absolute top-1/2 left-[calc(100%+8px)] z-100 block w-60 max-w-[calc(100vw-48px)] -translate-y-1/2 translate-x-1 rounded-[5px] bg-ttp21-pill px-2.75 py-1.75 text-[11.5px] leading-[1.4] text-white opacity-0 transition-[opacity,translate,visibility] duration-150 ease-[cubic-bezier(0.22,1,0.36,1)] after:absolute after:top-1/2 after:right-full after:-translate-y-1/2 after:border-4 after:border-transparent after:border-r-ttp21-pill after:content-[''] group-hover:visible group-hover:translate-x-0 group-hover:opacity-100 group-focus-within:visible group-focus-within:translate-x-0 group-focus-within:opacity-100;
}
.ttp-21 .ttp21-input {
@apply w-full rounded-md border border-ttp21-chip bg-[oklch(0.98_0_0)] px-2.75 py-2 text-[12.5px] text-ttp21-field outline-none focus:border-ttp21-focus focus:bg-white;
}
/* Under 640px there is no room to the side: the pill anchors above
the label and the arrow moves to the bottom edge. */
@media (max-width: 639px) {
.ttp-21 .ttp21-tip {
@apply bottom-[calc(100%+8px)] top-auto left-0 w-[calc(100vw-40px)] max-w-[280px] translate-x-0 translate-y-1 group-hover:translate-y-0 group-focus-within:translate-y-0 after:top-full after:right-auto after:left-3 after:translate-y-0 after:border-r-transparent after:border-t-ttp21-pill;
}
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-ttp21-ink: #111111;
--color-ttp21-ink: oklch(0.18 0 0);
--color-ttp21-field: #374151;
--color-ttp21-field: oklch(0.37 0.03 260);
--color-ttp21-icon: #6b7280;
--color-ttp21-icon: oklch(0.55 0.02 260);
--color-ttp21-chip: #e5e7eb;
--color-ttp21-chip: oklch(0.92 0.005 260);
--color-ttp21-chiphover: #cbd0d8;
--color-ttp21-chiphover: oklch(0.85 0.008 260);
--color-ttp21-pill: #18181b;
--color-ttp21-pill: oklch(0.21 0.005 285);
--color-ttp21-focus: #4f46e5;
--color-ttp21-focus: oklch(0.51 0.21 277);
--font-ttp21-sans: system-ui, -apple-system, sans-serif;
}
.ttp-21 {
width: 100%;
min-height: 100svh;
}
/* Five fields, one help affordance. */
.ttp-21 .ttp21-label {
@apply mb-1.5 flex items-center gap-1.25 text-[12.5px] font-medium text-ttp21-ink;
}
.ttp-21 .ttp21-icon {
@apply inline-flex h-4 w-4 shrink-0 cursor-help items-center justify-center rounded-full border-0 bg-ttp21-chip p-0 text-[9.5px] font-bold leading-none text-ttp21-icon transition-colors duration-100 hover:bg-ttp21-chiphover hover:text-ttp21-field focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ttp21-focus;
}
.ttp-21 .ttp21-tip {
@apply pointer-events-none invisible absolute top-1/2 left-[calc(100%+8px)] z-100 block w-60 max-w-[calc(100vw-48px)] -translate-y-1/2 translate-x-1 rounded-[5px] bg-ttp21-pill px-2.75 py-1.75 text-[11.5px] leading-[1.4] text-white opacity-0 transition-[opacity,translate,visibility] duration-150 ease-[cubic-bezier(0.22,1,0.36,1)] after:absolute after:top-1/2 after:right-full after:-translate-y-1/2 after:border-4 after:border-transparent after:border-r-ttp21-pill after:content-[''] group-hover:visible group-hover:translate-x-0 group-hover:opacity-100 group-focus-within:visible group-focus-within:translate-x-0 group-focus-within:opacity-100;
}
.ttp-21 .ttp21-input {
@apply w-full rounded-md border border-ttp21-chip bg-[oklch(0.98_0_0)] px-2.75 py-2 text-[12.5px] text-ttp21-field outline-none focus:border-ttp21-focus focus:bg-white;
}
/* Under 640px there is no room to the side: the pill anchors above
the label and the arrow moves to the bottom edge. */
@media (max-width: 639px) {
.ttp-21 .ttp21-tip {
@apply bottom-[calc(100%+8px)] top-auto left-0 w-[calc(100vw-40px)] max-w-[280px] translate-x-0 translate-y-1 group-hover:translate-y-0 group-focus-within:translate-y-0 after:top-full after:right-auto after:left-3 after:translate-y-0 after:border-r-transparent after:border-t-ttp21-pill;
}
}@import "tailwindcss";
@theme {
--color-ttp21-ink: #111111;
--color-ttp21-ink: oklch(0.18 0 0);
--color-ttp21-field: #374151;
--color-ttp21-field: oklch(0.37 0.03 260);
--color-ttp21-icon: #6b7280;
--color-ttp21-icon: oklch(0.55 0.02 260);
--color-ttp21-chip: #e5e7eb;
--color-ttp21-chip: oklch(0.92 0.005 260);
--color-ttp21-chiphover: #cbd0d8;
--color-ttp21-chiphover: oklch(0.85 0.008 260);
--color-ttp21-pill: #18181b;
--color-ttp21-pill: oklch(0.21 0.005 285);
--color-ttp21-focus: #4f46e5;
--color-ttp21-focus: oklch(0.51 0.21 277);
--font-ttp21-sans: system-ui, -apple-system, sans-serif;
}
.ttp-21 {
width: 100%;
min-height: 100svh;
}
/* Five fields, one help affordance. */
.ttp-21 .ttp21-label {
@apply mb-1.5 flex items-center gap-1.25 text-[12.5px] font-medium text-ttp21-ink;
}
.ttp-21 .ttp21-icon {
@apply inline-flex h-4 w-4 shrink-0 cursor-help items-center justify-center rounded-full border-0 bg-ttp21-chip p-0 text-[9.5px] font-bold leading-none text-ttp21-icon transition-colors duration-100 hover:bg-ttp21-chiphover hover:text-ttp21-field focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ttp21-focus;
}
.ttp-21 .ttp21-tip {
@apply pointer-events-none invisible absolute top-1/2 left-[calc(100%+8px)] z-100 block w-60 max-w-[calc(100vw-48px)] -translate-y-1/2 translate-x-1 rounded-[5px] bg-ttp21-pill px-2.75 py-1.75 text-[11.5px] leading-[1.4] text-white opacity-0 transition-[opacity,translate,visibility] duration-150 ease-[cubic-bezier(0.22,1,0.36,1)] after:absolute after:top-1/2 after:right-full after:-translate-y-1/2 after:border-4 after:border-transparent after:border-r-ttp21-pill after:content-[''] group-hover:visible group-hover:translate-x-0 group-hover:opacity-100 group-focus-within:visible group-focus-within:translate-x-0 group-focus-within:opacity-100;
}
.ttp-21 .ttp21-input {
@apply w-full rounded-md border border-ttp21-chip bg-[oklch(0.98_0_0)] px-2.75 py-2 text-[12.5px] text-ttp21-field outline-none focus:border-ttp21-focus focus:bg-white;
}
/* Under 640px there is no room to the side: the pill anchors above
the label and the arrow moves to the bottom edge. */
@media (max-width: 639px) {
.ttp-21 .ttp21-tip {
@apply bottom-[calc(100%+8px)] top-auto left-0 w-[calc(100vw-40px)] max-w-[280px] translate-x-0 translate-y-1 group-hover:translate-y-0 group-focus-within:translate-y-0 after:top-full after:right-auto after:left-3 after:translate-y-0 after:border-r-transparent after:border-t-ttp21-pill;
}
}What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The one correction that matters: the hint's id is referenced by the
<input>'saria-describedby, not just the icon's. A form hint that only exists on hover is a hint most users never receive. - Same triangle recipe as demo 20 rotated to point left, then rotated again under
max-sm:when the pill flips above — three border utilities per direction, no SVG anywhere. - UI change from the CSS original: the question mark is a real
<button>witharia-label, labels are bound to inputs withfor/id, the glyph grey went#9ca3af→#6b7280(2.3:1 → 4.7:1 on the chip) at 9.5px, the focus border is a darker indigo, and the pill flips above the field below 640px instead of overflowing.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Form Field Help Tooltip with Side-Anchored Hint Pill
Source: https://codefronts.com/snippets/tailwind-css-tooltips/tailwind-form-field-help-tooltip-side-anchored-hint-pill/
Small help circles beside form labels that reveal a one-line rule to the right, so the hint never covers the field above. The port's correction is semantic: the hint is wired to the INPUT with aria-describedby, which means a screen reader reads the rule while typing — not only when something is hovered.
## HTML
```html
<section class="ttp-21 w-full min-h-svh bg-white py-10 pr-[320px] pl-7 font-ttp21-sans max-xl:pr-7">
<form class="mx-auto max-w-[400px]">
<div class="mb-4.5">
<label for="ttp21-f1" class="ttp21-label">Username
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the username field" aria-describedby="ttp21-h1" class="ttp21-icon">?</button>
<span id="ttp21-h1" role="tooltip" class="ttp21-tip">Letters, numbers and underscores only. 3–20 characters.</span>
</span>
</label>
<input id="ttp21-f1" aria-describedby="ttp21-h1" class="ttp21-input" type="text" value="kalani_a" readonly>
</div>
<div class="mb-4.5">
<label for="ttp21-f2" class="ttp21-label">Email address
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the email field" aria-describedby="ttp21-h2" class="ttp21-icon">?</button>
<span id="ttp21-h2" role="tooltip" class="ttp21-tip">Used for login and account recovery. Never shown publicly.</span>
</span>
</label>
<input id="ttp21-f2" aria-describedby="ttp21-h2" class="ttp21-input" type="email" value="kalani@example.com" readonly>
</div>
<div class="mb-4.5">
<label for="ttp21-f3" class="ttp21-label">Website
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the website field" aria-describedby="ttp21-h3" class="ttp21-icon">?</button>
<span id="ttp21-h3" role="tooltip" class="ttp21-tip">Include https:// — displayed on your public profile page.</span>
</span>
</label>
<input id="ttp21-f3" aria-describedby="ttp21-h3" class="ttp21-input" type="text" value="https://kalani.dev" readonly>
</div>
<div class="mb-4.5">
<label for="ttp21-f4" class="ttp21-label">Bio
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the bio field" aria-describedby="ttp21-h4" class="ttp21-icon">?</button>
<span id="ttp21-h4" role="tooltip" class="ttp21-tip">Shown below your name on your profile. Max 160 characters.</span>
</span>
</label>
<input id="ttp21-f4" aria-describedby="ttp21-h4" class="ttp21-input" type="text" value="Building things on the web. Based in Honolulu." readonly>
</div>
<div>
<label for="ttp21-f5" class="ttp21-label">Time zone
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the time zone field" aria-describedby="ttp21-h5" class="ttp21-icon">?</button>
<span id="ttp21-h5" role="tooltip" class="ttp21-tip">Used to display timestamps and schedule notifications correctly.</span>
</span>
</label>
<input id="ttp21-f5" aria-describedby="ttp21-h5" class="ttp21-input" type="text" value="Pacific/Honolulu (UTC −10:00)" readonly>
</div>
</form>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-ttp21-ink: #111111;
--color-ttp21-ink: oklch(0.18 0 0);
--color-ttp21-field: #374151;
--color-ttp21-field: oklch(0.37 0.03 260);
--color-ttp21-icon: #6b7280;
--color-ttp21-icon: oklch(0.55 0.02 260);
--color-ttp21-chip: #e5e7eb;
--color-ttp21-chip: oklch(0.92 0.005 260);
--color-ttp21-chiphover: #cbd0d8;
--color-ttp21-chiphover: oklch(0.85 0.008 260);
--color-ttp21-pill: #18181b;
--color-ttp21-pill: oklch(0.21 0.005 285);
--color-ttp21-focus: #4f46e5;
--color-ttp21-focus: oklch(0.51 0.21 277);
--font-ttp21-sans: system-ui, -apple-system, sans-serif;
}
.ttp-21 {
width: 100%;
min-height: 100svh;
}
/* Five fields, one help affordance. */
.ttp-21 .ttp21-label {
@apply mb-1.5 flex items-center gap-1.25 text-[12.5px] font-medium text-ttp21-ink;
}
.ttp-21 .ttp21-icon {
@apply inline-flex h-4 w-4 shrink-0 cursor-help items-center justify-center rounded-full border-0 bg-ttp21-chip p-0 text-[9.5px] font-bold leading-none text-ttp21-icon transition-colors duration-100 hover:bg-ttp21-chiphover hover:text-ttp21-field focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ttp21-focus;
}
.ttp-21 .ttp21-tip {
@apply pointer-events-none invisible absolute top-1/2 left-[calc(100%+8px)] z-100 block w-60 max-w-[calc(100vw-48px)] -translate-y-1/2 translate-x-1 rounded-[5px] bg-ttp21-pill px-2.75 py-1.75 text-[11.5px] leading-[1.4] text-white opacity-0 transition-[opacity,translate,visibility] duration-150 ease-[cubic-bezier(0.22,1,0.36,1)] after:absolute after:top-1/2 after:right-full after:-translate-y-1/2 after:border-4 after:border-transparent after:border-r-ttp21-pill after:content-[''] group-hover:visible group-hover:translate-x-0 group-hover:opacity-100 group-focus-within:visible group-focus-within:translate-x-0 group-focus-within:opacity-100;
}
.ttp-21 .ttp21-input {
@apply w-full rounded-md border border-ttp21-chip bg-[oklch(0.98_0_0)] px-2.75 py-2 text-[12.5px] text-ttp21-field outline-none focus:border-ttp21-focus focus:bg-white;
}
/* Under 640px there is no room to the side: the pill anchors above
the label and the arrow moves to the bottom edge. */
@media (max-width: 639px) {
.ttp-21 .ttp21-tip {
@apply bottom-[calc(100%+8px)] top-auto left-0 w-[calc(100vw-40px)] max-w-[280px] translate-x-0 translate-y-1 group-hover:translate-y-0 group-focus-within:translate-y-0 after:top-full after:right-auto after:left-3 after:translate-y-0 after:border-r-transparent after:border-t-ttp21-pill;
}
}
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Form Field Help Tooltip with Side-Anchored Hint Pill
Source: https://codefronts.com/snippets/tailwind-css-tooltips/tailwind-form-field-help-tooltip-side-anchored-hint-pill/
Small help circles beside form labels that reveal a one-line rule to the right, so the hint never covers the field above. The port's correction is semantic: the hint is wired to the INPUT with aria-describedby, which means a screen reader reads the rule while typing — not only when something is hovered.
## HTML
```html
<section class="ttp-21 w-full min-h-svh bg-white py-10 pr-[320px] pl-7 font-ttp21-sans max-xl:pr-7">
<form class="mx-auto max-w-[400px]">
<div class="mb-4.5">
<label for="ttp21-f1" class="ttp21-label">Username
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the username field" aria-describedby="ttp21-h1" class="ttp21-icon">?</button>
<span id="ttp21-h1" role="tooltip" class="ttp21-tip">Letters, numbers and underscores only. 3–20 characters.</span>
</span>
</label>
<input id="ttp21-f1" aria-describedby="ttp21-h1" class="ttp21-input" type="text" value="kalani_a" readonly>
</div>
<div class="mb-4.5">
<label for="ttp21-f2" class="ttp21-label">Email address
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the email field" aria-describedby="ttp21-h2" class="ttp21-icon">?</button>
<span id="ttp21-h2" role="tooltip" class="ttp21-tip">Used for login and account recovery. Never shown publicly.</span>
</span>
</label>
<input id="ttp21-f2" aria-describedby="ttp21-h2" class="ttp21-input" type="email" value="kalani@example.com" readonly>
</div>
<div class="mb-4.5">
<label for="ttp21-f3" class="ttp21-label">Website
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the website field" aria-describedby="ttp21-h3" class="ttp21-icon">?</button>
<span id="ttp21-h3" role="tooltip" class="ttp21-tip">Include https:// — displayed on your public profile page.</span>
</span>
</label>
<input id="ttp21-f3" aria-describedby="ttp21-h3" class="ttp21-input" type="text" value="https://kalani.dev" readonly>
</div>
<div class="mb-4.5">
<label for="ttp21-f4" class="ttp21-label">Bio
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the bio field" aria-describedby="ttp21-h4" class="ttp21-icon">?</button>
<span id="ttp21-h4" role="tooltip" class="ttp21-tip">Shown below your name on your profile. Max 160 characters.</span>
</span>
</label>
<input id="ttp21-f4" aria-describedby="ttp21-h4" class="ttp21-input" type="text" value="Building things on the web. Based in Honolulu." readonly>
</div>
<div>
<label for="ttp21-f5" class="ttp21-label">Time zone
<span class="group relative inline-flex items-center">
<button type="button" aria-label="About the time zone field" aria-describedby="ttp21-h5" class="ttp21-icon">?</button>
<span id="ttp21-h5" role="tooltip" class="ttp21-tip">Used to display timestamps and schedule notifications correctly.</span>
</span>
</label>
<input id="ttp21-f5" aria-describedby="ttp21-h5" class="ttp21-input" type="text" value="Pacific/Honolulu (UTC −10:00)" readonly>
</div>
</form>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-ttp21-ink: #111111;
--color-ttp21-ink: oklch(0.18 0 0);
--color-ttp21-field: #374151;
--color-ttp21-field: oklch(0.37 0.03 260);
--color-ttp21-icon: #6b7280;
--color-ttp21-icon: oklch(0.55 0.02 260);
--color-ttp21-chip: #e5e7eb;
--color-ttp21-chip: oklch(0.92 0.005 260);
--color-ttp21-chiphover: #cbd0d8;
--color-ttp21-chiphover: oklch(0.85 0.008 260);
--color-ttp21-pill: #18181b;
--color-ttp21-pill: oklch(0.21 0.005 285);
--color-ttp21-focus: #4f46e5;
--color-ttp21-focus: oklch(0.51 0.21 277);
--font-ttp21-sans: system-ui, -apple-system, sans-serif;
}
.ttp-21 {
width: 100%;
min-height: 100svh;
}
/* Five fields, one help affordance. */
.ttp-21 .ttp21-label {
@apply mb-1.5 flex items-center gap-1.25 text-[12.5px] font-medium text-ttp21-ink;
}
.ttp-21 .ttp21-icon {
@apply inline-flex h-4 w-4 shrink-0 cursor-help items-center justify-center rounded-full border-0 bg-ttp21-chip p-0 text-[9.5px] font-bold leading-none text-ttp21-icon transition-colors duration-100 hover:bg-ttp21-chiphover hover:text-ttp21-field focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ttp21-focus;
}
.ttp-21 .ttp21-tip {
@apply pointer-events-none invisible absolute top-1/2 left-[calc(100%+8px)] z-100 block w-60 max-w-[calc(100vw-48px)] -translate-y-1/2 translate-x-1 rounded-[5px] bg-ttp21-pill px-2.75 py-1.75 text-[11.5px] leading-[1.4] text-white opacity-0 transition-[opacity,translate,visibility] duration-150 ease-[cubic-bezier(0.22,1,0.36,1)] after:absolute after:top-1/2 after:right-full after:-translate-y-1/2 after:border-4 after:border-transparent after:border-r-ttp21-pill after:content-[''] group-hover:visible group-hover:translate-x-0 group-hover:opacity-100 group-focus-within:visible group-focus-within:translate-x-0 group-focus-within:opacity-100;
}
.ttp-21 .ttp21-input {
@apply w-full rounded-md border border-ttp21-chip bg-[oklch(0.98_0_0)] px-2.75 py-2 text-[12.5px] text-ttp21-field outline-none focus:border-ttp21-focus focus:bg-white;
}
/* Under 640px there is no room to the side: the pill anchors above
the label and the arrow moves to the bottom edge. */
@media (max-width: 639px) {
.ttp-21 .ttp21-tip {
@apply bottom-[calc(100%+8px)] top-auto left-0 w-[calc(100vw-40px)] max-w-[280px] translate-x-0 translate-y-1 group-hover:translate-y-0 group-focus-within:translate-y-0 after:top-full after:right-auto after:left-3 after:translate-y-0 after:border-r-transparent after:border-t-ttp21-pill;
}
}
```How this works
The hint belongs to the input, not the icon. A sighted user hovers the question mark; a screen-reader user hears the rule as part of the field's description because aria-describedby lives on the <input>. Same content, two delivery paths, one source of truth.
Rightward placement is the point. An upward pill would cover the previous field's input; opening to the side keeps the whole form readable while the hint is up.
The trigger is a button inside the label, so it is tabbable in the natural field order and the pill answers group-focus-within:.
Make it yours
- Icon: 15px circle, 9px glyph. Swap the
?for an SVG info mark and keep the flex centring. - Pill width
w-60; it wraps, unlike the toolbar variant, so longer rules are fine. - Error variant: add
bg-ttp21-errorto the pill andafter:border-r-ttp21-errorto the arrow — two utilities, same geometry.
Gotchas — read before shipping
- A side pill needs right-hand room; under 640px this port anchors it above the label and clamps it to the viewport.
- The original's
#9ca3afglyph on#e5e7ebis about 2.3:1 — a help icon is content, so it was darkened to a 0.45-lightness grey. pointer-events-noneis right for a one-line rule but wrong the moment the hint contains a link; that case wants a click-toggled popover.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
inline-flex, CSS triangles and :focus-within; the floor quoted is Tailwind v4's own generated sheet.