A weekly bar chart that shows each day's value in a tooltip above the bar you hover while the other bars dim. With no bar hovered, the highlighted day keeps its tooltip, using :has().
Also known as: chart tooltip css, data point tooltip, graph hover value.
<span class="tip-bar-chart"> <span class="tip-bar-chart-bar" tabindex="0" role="img" aria-label="Monday: 1,120 visits" data-day="M" style="--v: 45%"><span class="tip-bar-chart-tip" role="tooltip" aria-hidden="true">Mon <b>1,120</b></span></span> <span class="tip-bar-chart-bar" tabindex="0" role="img" aria-label="Tuesday: 1,540 visits" data-day="T" style="--v: 62%"><span class="tip-bar-chart-tip" role="tooltip" aria-hidden="true">Tue <b>1,540</b></span></span> <span class="tip-bar-chart-bar" tabindex="0" role="img" aria-label="Wednesday: 950 visits" data-day="W" style="--v: 38%"><span class="tip-bar-chart-tip" role="tooltip" aria-hidden="true">Wed <b>950</b></span></span> <span class="tip-bar-chart-bar" tabindex="0" role="img" aria-label="Thursday: 2,130 visits" data-day="T" style="--v: 85%"><span class="tip-bar-chart-tip" role="tooltip" aria-hidden="true">Thu <b>2,130</b></span></span> <span class="tip-bar-chart-bar" tabindex="0" role="img" aria-label="Friday: 1,760 visits" data-day="F" style="--v: 70%"><span class="tip-bar-chart-tip" role="tooltip" aria-hidden="true">Fri <b>1,760</b></span></span> <span class="tip-bar-chart-bar" tabindex="0" role="img" aria-label="Saturday: 1,300 visits" data-day="S" style="--v: 52%"><span class="tip-bar-chart-tip" role="tooltip" aria-hidden="true">Sat <b>1,300</b></span></span> <span class="tip-bar-chart-bar" tabindex="0" role="img" aria-label="Sunday: 760 visits" data-day="S" style="--v: 30%"><span class="tip-bar-chart-tip" role="tooltip" aria-hidden="true">Sun <b>760</b></span></span> </span>
.tip-bar-chart {
--c: var(--tip-color, #3b82f6);
font-size: var(--tip-size, 16px);
position: relative;
display: inline-flex;
align-items: flex-end;
gap: 0.35em;
margin-top: 1em; /* room for the tooltip in this preview */
padding: 0 0.4em 1.5em;
/* the baseline under the bars */
background: linear-gradient(color-mix(in srgb, var(--c) 35%, transparent), color-mix(in srgb, var(--c) 35%, transparent)) 0 calc(100% - 1.5em) / 100% 1px no-repeat;
font-family: system-ui, sans-serif;
}
/* the whole column is the hover target; the visible bar is ::before */
.tip-bar-chart-bar {
position: relative;
width: 1.8em;
height: 7em;
border-radius: 0.4em 0.4em 0 0;
outline: none;
transition: background-color 0.2s ease;
}
.tip-bar-chart-bar::before {
content: "";
position: absolute;
right: 0.25em;
bottom: 0;
left: 0.25em;
height: var(--v);
border-radius: 0.3em 0.3em 0 0;
background: var(--c);
transition: opacity 0.25s ease, filter 0.25s ease;
}
.tip-bar-chart-bar::after {
content: attr(data-day);
position: absolute;
top: calc(100% + 0.4em);
left: 50%;
color: color-mix(in srgb, currentColor 50%, transparent);
font-size: 0.7em;
font-weight: 600;
transform: translateX(-50%);
}
.tip-bar-chart-bar:focus-visible { background: color-mix(in srgb, var(--c) 10%, transparent); }
.tip-bar-chart-bar:focus-visible::before { outline: 2px solid var(--c); outline-offset: 2px; }
.tip-bar-chart-tip {
position: absolute;
bottom: calc(var(--v) + 0.6em);
left: 50%;
z-index: 10;
padding: 0.35em 0.6em;
border-radius: 0.4em;
background: color-mix(in srgb, var(--c) 14%, #0f172a);
box-shadow: 0 0.4em 0.8em -0.3em rgb(0 0 0 / 0.5);
color: rgb(255 255 255 / 0.65);
font-size: 0.75em;
font-weight: 500;
line-height: 1.3;
white-space: nowrap;
opacity: 0;
visibility: hidden;
pointer-events: none;
transform-origin: 50% 100%;
transform: translate(-50%, 0.3em) scale(0.9);
transition: opacity 0.15s ease, transform 0.25s cubic-bezier(0.3, 1.4, 0.5, 1), visibility 0.25s;
}
.tip-bar-chart-tip b {
margin-left: 0.2em;
color: #fff;
font-weight: 700;
}
.tip-bar-chart-tip::before {
content: "";
position: absolute;
top: 100%;
left: 50%;
width: 0.6em;
height: 0.6em;
background: inherit;
transform: translate(-50%, -50%) rotate(45deg);
}
/* once the chart is hovered or focused, every bar dims except the active one */
.tip-bar-chart:hover .tip-bar-chart-bar::before,
.tip-bar-chart:focus-within .tip-bar-chart-bar::before { opacity: 0.35; }
.tip-bar-chart .tip-bar-chart-bar:hover::before,
.tip-bar-chart .tip-bar-chart-bar:focus-visible::before,
.tip-bar-chart:hover:not(:has(.tip-bar-chart-bar:hover, .tip-bar-chart-bar:focus-visible)) .tip-bar-chart-bar:nth-child(4)::before {
opacity: 1;
filter: saturate(1.3) brightness(1.05);
}
.tip-bar-chart-bar:hover .tip-bar-chart-tip,
.tip-bar-chart-bar:focus-visible .tip-bar-chart-tip,
.tip-bar-chart:hover:not(:has(.tip-bar-chart-bar:hover, .tip-bar-chart-bar:focus-visible)) .tip-bar-chart-bar:nth-child(4) .tip-bar-chart-tip {
opacity: 1;
visibility: visible;
transform: translate(-50%, 0) scale(1);
}
@media (prefers-reduced-motion: reduce) {
.tip-bar-chart, .tip-bar-chart *, .tip-bar-chart::before, .tip-bar-chart::after, .tip-bar-chart *::before, .tip-bar-chart *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--tip-color on the element or a parent.
Currently previewing #3b82f6.--tip-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 16px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.