Tooltip

A floating label that appears on hover to describe an element.

View source
import * as Tooltip from "@danielfrg/solid-ui/tooltip"
import styles from "./index.module.css"

export function DemoTooltipHero() {
  return (
    <div class={styles.panel}>
      <Tooltip.Root openDelay={200} placement="top">
        <Tooltip.Trigger class={styles.button}>
          <BoldIcon />
        </Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Content class={styles.popup}>
            <Tooltip.Arrow />
            Bold
          </Tooltip.Content>
        </Tooltip.Portal>
      </Tooltip.Root>

      <Tooltip.Root openDelay={200} placement="top">
        <Tooltip.Trigger class={styles.button}>
          <ItalicIcon />
        </Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Content class={styles.popup}>
            <Tooltip.Arrow />
            Italic
          </Tooltip.Content>
        </Tooltip.Portal>
      </Tooltip.Root>

      <Tooltip.Root openDelay={200} placement="top">
        <Tooltip.Trigger class={styles.button}>
          <UnderlineIcon />
        </Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Content class={styles.popup}>
            <Tooltip.Arrow />
            Underline
          </Tooltip.Content>
        </Tooltip.Portal>
      </Tooltip.Root>
    </div>
  )
}

function BoldIcon() {
  return (
    <svg width="16" height="16" viewBox="0 0 16 16" fill="currentcolor">
      <path d="M3.73353 2.13333C3.4386 2.13333 3.2002 2.37226 3.2002 2.66666C3.2002 2.96106 3.4386 3.2 3.73353 3.2H4.26686V12.8H3.73353C3.4386 12.8 3.2002 13.0389 3.2002 13.3333C3.2002 13.6277 3.4386 13.8667 3.73353 13.8667H9.86686C11.7783 13.8667 13.3335 12.3115 13.3335 10.4C13.3335 8.9968 12.4945 7.78881 11.2929 7.24375C11.8897 6.70615 12.2669 5.93066 12.2669 5.06666C12.2669 3.44906 10.9506 2.13333 9.33353 2.13333H3.73353ZM6.93353 3.2H8.26686C9.29619 3.2 10.1335 4.03733 10.1335 5.06666C10.1335 6.096 9.29619 6.93333 8.26686 6.93333H6.93353V3.2ZM6.93353 8H7.73353H8.26686C9.59006 8 10.6669 9.0768 10.6669 10.4C10.6669 11.7232 9.59006 12.8 8.26686 12.8H6.93353V8Z" />
    </svg>
  )
}

function ItalicIcon() {
  return (
    <svg width="16" height="16" viewBox="0 0 16 16" fill="currentcolor">
      <path d="M8.52599 2.12186C8.48583 2.12267 8.44578 2.1265 8.4062 2.13332H6.93328C6.63835 2.13332 6.39995 2.37225 6.39995 2.66665C6.39995 2.96105 6.63835 3.19999 6.93328 3.19999H7.70099L6.69057 12.8H5.86661C5.57168 12.8 5.33328 13.0389 5.33328 13.3333C5.33328 13.6277 5.57168 13.8667 5.86661 13.8667H9.06661C9.36155 13.8667 9.59995 13.6277 9.59995 13.3333C9.59995 13.0389 9.36155 12.8 9.06661 12.8H8.2989L9.30932 3.19999H10.1333C10.4282 3.19999 10.6666 2.96105 10.6666 2.66665C10.6666 2.37225 10.4282 2.13332 10.1333 2.13332H8.66349C8.61807 2.12555 8.57207 2.12171 8.52599 2.12186Z" />
    </svg>
  )
}

function UnderlineIcon() {
  return (
    <svg width="16" height="16" viewBox="0 0 16 16" fill="currentcolor">
      <path d="M3.73331 2.13332C3.43838 2.13332 3.19998 2.37225 3.19998 2.66666C3.19998 2.96106 3.43838 3.19999 3.73331 3.19999V7.99999C3.73331 10.224 5.55144 12.2667 7.99998 12.2667C10.4485 12.2667 12.2666 10.224 12.2666 7.99999V3.19999C12.5616 3.19999 12.8 2.96106 12.8 2.66666C12.8 2.37225 12.5616 2.13332 12.2666 2.13332H10.1333C9.83838 2.13332 9.59998 2.37225 9.59998 2.66666C9.59998 2.96106 9.83838 3.19999 10.1333 3.19999V8.97187C10.1333 10.0855 9.32179 11.0818 8.21352 11.1896C6.94152 11.3138 5.86665 10.3136 5.86665 9.06666V3.19999C6.16158 3.19999 6.39998 2.96106 6.39998 2.66666C6.39998 2.37225 6.16158 2.13332 5.86665 2.13332H3.73331ZM3.73331 13.3333C3.43838 13.3333 3.19998 13.5723 3.19998 13.8667C3.19998 14.1611 3.43838 14.4 3.73331 14.4H12.2666C12.5616 14.4 12.8 14.1611 12.8 13.8667C12.8 13.5723 12.5616 13.3333 12.2666 13.3333H3.73331Z" />
    </svg>
  )
}
.panel {
  display: flex;
  border: 1px solid var(--color-gray-200);
  background-color: var(--color-gray-50);
  border-radius: 0.375rem;
  padding: 0.125rem;
}

.button {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  outline: 0;
  border: 0;
  border-radius: 0.25rem;
  background-color: transparent;
  color: var(--color-gray-900);
  user-select: none;
  cursor: pointer;
}

@media (hover: hover) {
  .button:hover {
    background-color: var(--color-gray-100);
  }
}

.button:active {
  background-color: var(--color-gray-200);
}

.button:focus-visible {
  outline: 2px solid var(--color-blue);
  outline-offset: -1px;
}

.popup {
  box-sizing: border-box;
  font-size: 0.875rem;
  line-height: 1.25rem;
  display: flex;
  flex-direction: column;
  padding: 0.25rem 0.5rem;
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  color: var(--color-gray-900);
  z-index: 50;
  outline: 1px solid var(--color-gray-200);
  box-shadow:
    0 10px 15px -3px var(--color-gray-200),
    0 4px 6px -4px var(--color-gray-200);
  animation: popupHide 150ms ease forwards;
}

.popup[data-expanded] {
  animation: popupShow 150ms ease;
}

@media (prefers-color-scheme: dark) {
  .popup {
    outline: 1px solid var(--color-gray-300);
    box-shadow: none;
  }
}

@keyframes popupShow {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes popupHide {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0;
    transform: scale(0.96);
  }
}

.status {
  margin: 0 0 0.75rem;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
}
:root {
  --color-blue: oklch(45% 50% 264deg);
  --color-red: oklch(50% 55% 31deg);
  --color-gray-50: oklch(98% 0.25% 264deg);
  --color-gray-100: oklch(12% 9.5% 264deg / 5%);
  --color-gray-200: oklch(12% 9% 264deg / 7%);
  --color-gray-300: oklch(12% 8.5% 264deg / 17%);
  --color-gray-400: oklch(12% 8% 264deg / 38%);
  --color-gray-500: oklch(12% 7.5% 264deg / 50%);
  --color-gray-600: oklch(12% 7% 264deg / 67%);
  --color-gray-700: oklch(12% 6% 264deg / 77%);
  --color-gray-800: oklch(12% 5% 264deg / 85%);
  --color-gray-900: oklch(12% 5% 264deg / 90%);
  --color-gray-950: oklch(12% 5% 264deg / 95%);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-blue: oklch(69% 50% 264deg);
    --color-red: oklch(80% 55% 31deg);
    --color-gray-50: oklch(17% 0.25% 264deg);
    --color-gray-100: oklch(28% 0.75% 264deg / 65%);
    --color-gray-200: oklch(29% 0.75% 264deg / 80%);
    --color-gray-300: oklch(35% 0.75% 264deg / 80%);
    --color-gray-400: oklch(47% 0.875% 264deg / 80%);
    --color-gray-500: oklch(64% 1% 264deg / 80%);
    --color-gray-600: oklch(82% 1% 264deg / 80%);
    --color-gray-700: oklch(92% 1.125% 264deg / 80%);
    --color-gray-800: oklch(93% 0.875% 264deg / 85%);
    --color-gray-900: oklch(95% 0.5% 264deg / 90%);
    --color-gray-950: oklch(94% 0.375% 264deg / 95%);
  }
}

Examples

Controlled

Use open and onOpenChange to control the tooltip visibility externally.

Tooltip is hidden.

View source
import { createSignal } from "solid-js"
import * as Tooltip from "@danielfrg/solid-ui/tooltip"
import styles from "./index.module.css"

export function DemoTooltipControlled() {
  const [open, setOpen] = createSignal(false)

  return (
    <div>
      <p class={styles.status}>Tooltip is {open() ? "showing" : "hidden"}.</p>
      <div class={styles.panel}>
        <Tooltip.Root open={open()} onOpenChange={setOpen} placement="top">
          <Tooltip.Trigger class={styles.button}>
            <BoldIcon />
          </Tooltip.Trigger>
          <Tooltip.Portal>
            <Tooltip.Content class={styles.popup}>
              <Tooltip.Arrow />
              Bold
            </Tooltip.Content>
          </Tooltip.Portal>
        </Tooltip.Root>
      </div>
    </div>
  )
}

function BoldIcon() {
  return (
    <svg width="16" height="16" viewBox="0 0 16 16" fill="currentcolor">
      <path d="M3.73353 2.13333C3.4386 2.13333 3.2002 2.37226 3.2002 2.66666C3.2002 2.96106 3.4386 3.2 3.73353 3.2H4.26686V12.8H3.73353C3.4386 12.8 3.2002 13.0389 3.2002 13.3333C3.2002 13.6277 3.4386 13.8667 3.73353 13.8667H9.86686C11.7783 13.8667 13.3335 12.3115 13.3335 10.4C13.3335 8.9968 12.4945 7.78881 11.2929 7.24375C11.8897 6.70615 12.2669 5.93066 12.2669 5.06666C12.2669 3.44906 10.9506 2.13333 9.33353 2.13333H3.73353ZM6.93353 3.2H8.26686C9.29619 3.2 10.1335 4.03733 10.1335 5.06666C10.1335 6.096 9.29619 6.93333 8.26686 6.93333H6.93353V3.2ZM6.93353 8H7.73353H8.26686C9.59006 8 10.6669 9.0768 10.6669 10.4C10.6669 11.7232 9.59006 12.8 8.26686 12.8H6.93353V8Z" />
    </svg>
  )
}
.panel {
  display: flex;
  border: 1px solid var(--color-gray-200);
  background-color: var(--color-gray-50);
  border-radius: 0.375rem;
  padding: 0.125rem;
}

.button {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  outline: 0;
  border: 0;
  border-radius: 0.25rem;
  background-color: transparent;
  color: var(--color-gray-900);
  user-select: none;
  cursor: pointer;
}

@media (hover: hover) {
  .button:hover {
    background-color: var(--color-gray-100);
  }
}

.button:active {
  background-color: var(--color-gray-200);
}

.button:focus-visible {
  outline: 2px solid var(--color-blue);
  outline-offset: -1px;
}

.popup {
  box-sizing: border-box;
  font-size: 0.875rem;
  line-height: 1.25rem;
  display: flex;
  flex-direction: column;
  padding: 0.25rem 0.5rem;
  border-radius: 0.375rem;
  background-color: var(--color-gray-50);
  color: var(--color-gray-900);
  z-index: 50;
  outline: 1px solid var(--color-gray-200);
  box-shadow:
    0 10px 15px -3px var(--color-gray-200),
    0 4px 6px -4px var(--color-gray-200);
  animation: popupHide 150ms ease forwards;
}

.popup[data-expanded] {
  animation: popupShow 150ms ease;
}

@media (prefers-color-scheme: dark) {
  .popup {
    outline: 1px solid var(--color-gray-300);
    box-shadow: none;
  }
}

@keyframes popupShow {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes popupHide {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0;
    transform: scale(0.96);
  }
}

.status {
  margin: 0 0 0.75rem;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
}
:root {
  --color-blue: oklch(45% 50% 264deg);
  --color-red: oklch(50% 55% 31deg);
  --color-gray-50: oklch(98% 0.25% 264deg);
  --color-gray-100: oklch(12% 9.5% 264deg / 5%);
  --color-gray-200: oklch(12% 9% 264deg / 7%);
  --color-gray-300: oklch(12% 8.5% 264deg / 17%);
  --color-gray-400: oklch(12% 8% 264deg / 38%);
  --color-gray-500: oklch(12% 7.5% 264deg / 50%);
  --color-gray-600: oklch(12% 7% 264deg / 67%);
  --color-gray-700: oklch(12% 6% 264deg / 77%);
  --color-gray-800: oklch(12% 5% 264deg / 85%);
  --color-gray-900: oklch(12% 5% 264deg / 90%);
  --color-gray-950: oklch(12% 5% 264deg / 95%);
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-blue: oklch(69% 50% 264deg);
    --color-red: oklch(80% 55% 31deg);
    --color-gray-50: oklch(17% 0.25% 264deg);
    --color-gray-100: oklch(28% 0.75% 264deg / 65%);
    --color-gray-200: oklch(29% 0.75% 264deg / 80%);
    --color-gray-300: oklch(35% 0.75% 264deg / 80%);
    --color-gray-400: oklch(47% 0.875% 264deg / 80%);
    --color-gray-500: oklch(64% 1% 264deg / 80%);
    --color-gray-600: oklch(82% 1% 264deg / 80%);
    --color-gray-700: oklch(92% 1.125% 264deg / 80%);
    --color-gray-800: oklch(93% 0.875% 264deg / 85%);
    --color-gray-900: oklch(95% 0.5% 264deg / 90%);
    --color-gray-950: oklch(94% 0.375% 264deg / 95%);
  }
}