21 CSS Tooltips21 / 21

Pure CSSMIT licensed

Form Field Help

Compact help circles sit next to form labels and reveal a one-line hint on hover — username rules, email visibility, time-zone purpose. The tooltip pops to the right with a side-arrow tail so the form line stays uninterrupted. For settings panels, sign-up forms, and profile editors.

Published

Live Demo
Try it

The code

<div class="tp-21">
  <div class="tp-21__form">
    <div class="tp-21__field">
      <div class="tp-21__label">
        Username
        <span class="tp-21__t">
          <span class="tp-21__icon">?</span>
          <span class="tp-21__tip">Letters, numbers, and underscores only. 3–20 characters.</span>
        </span>
      </div>
      <input class="tp-21__input" type="text" value="kalani_a" readonly>
    </div>
    <div class="tp-21__field">
      <div class="tp-21__label">
        Email address
        <span class="tp-21__t">
          <span class="tp-21__icon">?</span>
          <span class="tp-21__tip">Used for login and account recovery. Never shown publicly.</span>
        </span>
      </div>
      <input class="tp-21__input" type="email" value="kalani@example.com" readonly>
    </div>
    <div class="tp-21__field">
      <div class="tp-21__label">
        Website
        <span class="tp-21__t">
          <span class="tp-21__icon">?</span>
          <span class="tp-21__tip">Include https:// — displayed on your public profile page.</span>
        </span>
      </div>
      <input class="tp-21__input" type="text" value="https://kalani.dev" readonly>
    </div>
    <div class="tp-21__field">
      <div class="tp-21__label">
        Bio
        <span class="tp-21__t">
          <span class="tp-21__icon">?</span>
          <span class="tp-21__tip">Shown below your name on your profile. Max 160 characters.</span>
        </span>
      </div>
      <input class="tp-21__input" type="text" value="Building things on the web. Based in Honolulu." readonly>
    </div>
    <div class="tp-21__field tp-21__field-last">
      <div class="tp-21__label">
        Time zone
        <span class="tp-21__t">
          <span class="tp-21__icon">?</span>
          <span class="tp-21__tip">Used to display timestamps and schedule notifications correctly.</span>
        </span>
      </div>
      <input class="tp-21__input" type="text" value="Pacific/Honolulu (UTC −10:00)" readonly>
    </div>
  </div>
</div>
.tp-21 {
  width: 100%;
  min-height: 100vh;
  background: #fff;
  /* Right room — help tooltips pop to the RIGHT of each ? icon,
       ~280px wide single-line pills. Padding the right side gives them
       room to extend without getting clipped by the gallery card. */
  padding: 40px 320px 40px 28px;
  font-family: system-ui, -apple-system, sans-serif;
}

.tp-21__form {
  max-width: 400px;
  margin: 0 auto;
}

.tp-21__field {
  margin-bottom: 18px;
}

.tp-21__field-last {
  margin-bottom: 0;
}

.tp-21__label {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 12.5px;
  font-weight: 500;
  color: #111;
  margin-bottom: 6px;
}

.tp-21__t {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.tp-21__icon {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #e5e7eb;
  color: #9ca3af;
  font-size: 9px;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: help;
  transition: background .12s, color .12s;
  flex-shrink: 0;
  line-height: 1;
}

.tp-21__t:hover .tp-21__icon {
  background: #d1d5db;
  color: #374151;
}

.tp-21__tip {
  position: absolute;
  top: 50%;
  left: calc(100% + 8px);
  transform: translateY(-50%) translateX(4px);
  background: #18181b;
  color: #fff;
  font-size: 11.5px;
  padding: 7px 11px;
  border-radius: 5px;
  width: 240px;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity .14s, transform .14s cubic-bezier(0.22, 1, 0.36, 1), visibility 0s linear .14s;
  z-index: 100;
  line-height: 1.4;
  display: block;
}

.tp-21__t:hover .tp-21__tip {
  opacity: 1;
  visibility: visible;
  transform: translateY(-50%) translateX(0);
  transition-delay: 0s;
}

.tp-21__tip::after {
  content: '';
  position: absolute;
  top: 50%;
  right: 100%;
  transform: translateY(-50%);
  border: 4px solid transparent;
  border-right-color: #18181b;
}

.tp-21__input {
  width: 100%;
  padding: 8px 11px;
  border: 1px solid #e5e7eb;
  border-radius: 6px;
  font-size: 12.5px;
  color: #374151;
  background: #fafafa;
  outline: none;
  font-family: inherit;
}

.tp-21__input:focus {
  border-color: #6366f1;
  background: #fff;
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Tooltip 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: Form Field Help
Source: https://codefronts.com/snippets/css-tooltips/form-field-help/

Compact help circles sit next to form labels and reveal a one-line hint on hover — username rules, email visibility, time-zone purpose. The tooltip pops to the right with a side-arrow tail so the form line stays uninterrupted. For settings panels, sign-up forms, and profile editors.
## HTML
```html
<div class="tp-21">
  <div class="tp-21__form">
    <div class="tp-21__field">
      <div class="tp-21__label">
        Username
        <span class="tp-21__t">
          <span class="tp-21__icon">?</span>
          <span class="tp-21__tip">Letters, numbers, and underscores only. 3–20 characters.</span>
        </span>
      </div>
      <input class="tp-21__input" type="text" value="kalani_a" readonly>
    </div>
    <div class="tp-21__field">
      <div class="tp-21__label">
        Email address
        <span class="tp-21__t">
          <span class="tp-21__icon">?</span>
          <span class="tp-21__tip">Used for login and account recovery. Never shown publicly.</span>
        </span>
      </div>
      <input class="tp-21__input" type="email" value="kalani@example.com" readonly>
    </div>
    <div class="tp-21__field">
      <div class="tp-21__label">
        Website
        <span class="tp-21__t">
          <span class="tp-21__icon">?</span>
          <span class="tp-21__tip">Include https:// — displayed on your public profile page.</span>
        </span>
      </div>
      <input class="tp-21__input" type="text" value="https://kalani.dev" readonly>
    </div>
    <div class="tp-21__field">
      <div class="tp-21__label">
        Bio
        <span class="tp-21__t">
          <span class="tp-21__icon">?</span>
          <span class="tp-21__tip">Shown below your name on your profile. Max 160 characters.</span>
        </span>
      </div>
      <input class="tp-21__input" type="text" value="Building things on the web. Based in Honolulu." readonly>
    </div>
    <div class="tp-21__field tp-21__field-last">
      <div class="tp-21__label">
        Time zone
        <span class="tp-21__t">
          <span class="tp-21__icon">?</span>
          <span class="tp-21__tip">Used to display timestamps and schedule notifications correctly.</span>
        </span>
      </div>
      <input class="tp-21__input" type="text" value="Pacific/Honolulu (UTC −10:00)" readonly>
    </div>
  </div>
</div>
```
## CSS
```css
.tp-21 {
  width: 100%;
  min-height: 100vh;
  background: #fff;
  /* Right room — help tooltips pop to the RIGHT of each ? icon,
       ~280px wide single-line pills. Padding the right side gives them
       room to extend without getting clipped by the gallery card. */
  padding: 40px 320px 40px 28px;
  font-family: system-ui, -apple-system, sans-serif;
}

.tp-21__form {
  max-width: 400px;
  margin: 0 auto;
}

.tp-21__field {
  margin-bottom: 18px;
}

.tp-21__field-last {
  margin-bottom: 0;
}

.tp-21__label {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 12.5px;
  font-weight: 500;
  color: #111;
  margin-bottom: 6px;
}

.tp-21__t {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.tp-21__icon {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #e5e7eb;
  color: #9ca3af;
  font-size: 9px;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: help;
  transition: background .12s, color .12s;
  flex-shrink: 0;
  line-height: 1;
}

.tp-21__t:hover .tp-21__icon {
  background: #d1d5db;
  color: #374151;
}

.tp-21__tip {
  position: absolute;
  top: 50%;
  left: calc(100% + 8px);
  transform: translateY(-50%) translateX(4px);
  background: #18181b;
  color: #fff;
  font-size: 11.5px;
  padding: 7px 11px;
  border-radius: 5px;
  width: 240px;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity .14s, transform .14s cubic-bezier(0.22, 1, 0.36, 1), visibility 0s linear .14s;
  z-index: 100;
  line-height: 1.4;
  display: block;
}

.tp-21__t:hover .tp-21__tip {
  opacity: 1;
  visibility: visible;
  transform: translateY(-50%) translateX(0);
  transition-delay: 0s;
}

.tp-21__tip::after {
  content: '';
  position: absolute;
  top: 50%;
  right: 100%;
  transform: translateY(-50%);
  border: 4px solid transparent;
  border-right-color: #18181b;
}

.tp-21__input {
  width: 100%;
  padding: 8px 11px;
  border: 1px solid #e5e7eb;
  border-radius: 6px;
  font-size: 12.5px;
  color: #374151;
  background: #fafafa;
  outline: none;
  font-family: inherit;
}

.tp-21__input:focus {
  border-color: #6366f1;
  background: #fff;
}
```

How this works

The tp-21 component is the Stripe-checkout/Typeform inline help idiom. Structurally, each row is a tp-21__field that stacks a tp-21__label above a tp-21__input. The label is a flex row that keeps text and the question-mark icon on the same baseline via display: flex; align-items: center; gap: 5px, so the icon never disturbs the field spacing when tooltips are hidden. The icon lives inside tp-21__t, a relatively positioned inline-flex wrapper that anchors the sideways-popping tp-21__tip pill.

The trigger is tp-21__icon, a 15x15 grey circle rendered with border-radius: 50%, #e5e7eb background, and a 9px bold question-mark glyph. On hover, the background steps to #d1d5db and the glyph darkens to #374151 — a Tailwind-neutral hover recipe that reads clearly without breaking the form's colour system. Because the icon sits inline with the label text and shares its baseline, the field label stays visually stable whether the user hovers the icon or not.

The tooltip pops to the right instead of above, which is the correct anchor for a form line: an upward-popping tip would collide with the previous field's input. Positioning is top: 50% with left: calc(100% + 8px), plus translateY(-50%) for vertical centring and a 4px translateX slide-in. The pill is a 240px dark #18181b panel with 11.5px white body copy at 1.4 line-height, and a ::after triangle rendered by border-right-color pointing left toward the icon. Enter animation is 140ms and matches the toolbar; delayed visibility keeps the pill alive during exit.

Make it yours

  • Recolour the trigger by editing tp-21__icon's background and text tokens plus the :hover pair. The Tailwind neutrals slot into any brand system; keep the 15px diameter so the glyph stays legible at 9px.
  • Anchor the tip above the field for narrow containers by swapping top: 50%; left: calc(100% + 8px) for bottom: calc(100% + 8px); left: 50% and updating the ::after arrow to point down.
  • Widen the tip for longer prose by increasing the fixed width: 240px on tp-21__tip. The pill wraps naturally because white-space is not locked to nowrap, unlike the toolbar variant.
  • Add an error-state variant by introducing tp-21__icon--error (red bg + white glyph) and tp-21__tip--error (red bg with matching arrow tint). Reuse the existing positioning so the error tip lands in the same spot as the help tip.
  • Swap the question-mark for an info glyph or an SVG info icon by replacing the icon child. Keep the flex-centring on tp-21__icon and the 9px font-size baseline so the glyph optical-centres inside the circle.

Gotchas — read before shipping

  • The container reserves 320px of right padding for exactly the rightward-popping tips. Removing that padding when embedding the component inside a narrower shell will clip every tip against the frame edge; either preserve the padding or add a tp-21__tip--above variant for narrow contexts.
  • The tip is triggered on hover of tp-21__t, not the icon alone. That is intentional so the pill stays open while the pointer traverses the 8px gap. Verify your click-tracking analytics do not treat the wrapper as a separate target — it will double-count icon hovers.
  • Question-mark triggers with hover-only reveal violate SC 2.1.1 Keyboard for form users. Add tabindex="0" to each tp-21__t and mirror the :hover selectors with :focus-visible. Combine with an aria-describedby that points from the input to the tip's ID for screen-reader parity.
  • The pill uses pointer-events: none, which prevents users from selecting the hint text. That is fine for a short format hint ("Max 160 characters") but wrong if you plan to include links or copyable examples. For those cases, switch to a click-toggled popover and drop the pointer-events: none.
  • The readonly inputs in the sample markup preserve the demo state. Ship the component with real type="email" and autocomplete attributes; the CSS is agnostic, but keep the tp-21__input:focus border swap so users see the active field state Stripe-style.
  • Fixed 240px pill width does not respond to viewport. On sub-420px screens, the pill can push past the container even with 320px padding. Add an @media (max-width: 640px) block that flips tp-21__tip to anchor above the field and shrinks the width to calc(100vw - 40px).

Browser support

ChromeSafariFirefoxEdge
90+15+88+90+

Inline-flex, calc anchors, and the delayed visibility transition are all baseline. INP stays under 200ms because only two properties animate. iOS Safari surfaces the tip on :hover only when a stylus or Magic Keyboard is attached.

Techniques used in this demo

Search CodeFronts

Loading…