Data display
Glass vial meter
A vertical potion-vial meter whose liquid fills a wide bulb first and then rises into a narrow neck, with meniscus and sheen drawn as gradients and a pulsing low-state — all CSS, no images.
Reach for it when building
- remaining quota or budget displays
- game health and resource meters
- reading or scroll progress with personality
- countdown-to-limit warnings
- playful dashboards
- meter
- skeuomorphic
- css-only
- reduced-motion
- no-assets
<div class="gvm-row">
<div class="gvm-unit">
<div class="gvm-vial" role="meter" aria-valuemin="0" aria-valuemax="100" aria-valuenow="68" aria-label="Quota remaining">
<div class="gvm-neck"><div class="gvm-fill" style="height: 0%;"></div></div>
<div class="gvm-bulb"><div class="gvm-fill" style="height: 94.4%;"></div></div>
</div>
<p class="gvm-read">68 / 100</p>
</div>
<div class="gvm-unit">
<div class="gvm-vial gvm-low" role="meter" aria-valuemin="0" aria-valuemax="100" aria-valuenow="18" aria-label="Quota remaining, low">
<div class="gvm-neck"><div class="gvm-fill" style="height: 0%;"></div></div>
<div class="gvm-bulb"><div class="gvm-fill" style="height: 25%;"></div></div>
</div>
<p class="gvm-read">18 / 100 — low</p>
</div>
<div class="gvm-unit">
<div class="gvm-vial" role="meter" aria-valuemin="0" aria-valuemax="100" aria-valuenow="90" aria-label="Quota remaining, nearly full">
<div class="gvm-neck"><div class="gvm-fill" style="height: 64.3%;"></div></div>
<div class="gvm-bulb"><div class="gvm-fill" style="height: 100%;"></div></div>
</div>
<p class="gvm-read">90 / 100</p>
</div>
</div>Colors come from shared theme tokens — --surface, --ink, --border, --accent and friends — so this CSS carries no palette
of its own. Use Runnable file to copy the tokens along with it.
.gvm-row { display: flex; gap: 2.5rem; align-items: flex-end; }
.gvm-unit { display: flex; flex-direction: column; align-items: center; }
.gvm-vial { display: flex; flex-direction: column; align-items: center; }
.gvm-neck,
.gvm-bulb {
position: relative;
overflow: hidden;
background: var(--surface-2);
border: 2px solid color-mix(in srgb, var(--dim) 70%, var(--ink));
box-shadow: inset 0 3px 6px color-mix(in srgb, var(--ink) 30%, transparent);
}
.gvm-neck {
width: 1.1rem;
height: 1.75rem;
border-radius: 0.3rem 0.3rem 0 0;
border-bottom: none;
}
.gvm-bulb {
width: 2.75rem;
height: 4.5rem;
border-radius: 1.3rem 1.3rem 1rem 1rem;
}
/* Liquid: light top, body, darkened base — one gradient sells the depth.
The bulb holds 72% of the scale; the neck holds the last 28%, so the
inline heights are (value / 72) and ((value - 72) / 28) respectively. */
.gvm-fill {
position: absolute;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
180deg,
color-mix(in srgb, var(--bad) 70%, var(--surface)),
color-mix(in srgb, var(--bad) 45%, var(--surface)) 55%,
color-mix(in srgb, var(--bad) 60%, var(--ink))
);
transition: height 0.3s ease;
}
/* Meniscus: a light line riding the liquid surface. */
.gvm-fill::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
border-radius: 999px;
background: color-mix(in srgb, var(--surface) 60%, transparent);
}
/* Glass sheen: a vertical highlight streak on the bulb only. */
.gvm-bulb::after {
content: '';
position: absolute;
left: 15%;
top: 10%;
bottom: 14%;
width: 18%;
border-radius: 999px;
background: linear-gradient(
180deg,
color-mix(in srgb, var(--surface) 55%, transparent),
color-mix(in srgb, var(--surface) 8%, transparent)
);
pointer-events: none;
}
.gvm-low .gvm-fill {
box-shadow: 0 0 10px color-mix(in srgb, var(--bad) 60%, transparent);
animation: gvm-pulse 1.6s ease-in-out infinite;
}
@keyframes gvm-pulse {
0%, 100% { filter: brightness(1); }
50% { filter: brightness(1.18); }
}
.gvm-read {
margin: 0.4rem 0 0;
font-size: 0.9375rem;
color: var(--dim);
font-variant-numeric: tabular-nums;
}Paste this into an agent to rebuild the pattern from scratch.
Build a vertical meter shaped as a glass vial — a wide bulb below a narrow neck — instead of a plain bar, for values where a bit of personality is welcome: quotas, health, remaining budget.
The geometry is two stacked containers, both `overflow: hidden` with a sunken background, a 2px glassy border, and an inset top shadow. The bulb is roughly 2.75rem wide by 4.5rem tall with large top radii and slightly smaller bottom radii; the neck is 1.1rem wide, sits on top, and has no bottom border so the two read as one vessel.
The fill math is the pattern's core: the bulb represents the first 72% of the scale and the neck the remaining 28%. For a value v (0–100), the bulb's fill height is `min(100, v / 72 * 100)` percent and the neck's is `max(0, (v - 72) / 28 * 100)` percent — liquid visibly fills the bulb first, then climbs the neck. Fills are bottom-anchored absolute divs with inline percentage heights and a height transition.
Three gradient tricks sell the material, all derived from tokens with color-mix, no images: the liquid is a three-stop vertical gradient (lightened top, body, ink-darkened base); the meniscus is a 3px light line drawn as the fill's `::before` riding the liquid surface; the sheen is a vertical highlight streak on the bulb's `::after`, offset left, fading downward.
At or below 25%, add a low state: an outer glow via box-shadow in the danger color and a gentle brightness pulse animation. Do not gate the animation yourself if the page's base styles already neutralize animation under `prefers-reduced-motion`; otherwise wrap it in the motion media query.
Expose the value with `role="meter"` and `aria-valuenow/min/max`, and print the numeric readout beneath in tabular figures — the vial is the mood, the number is the truth.
Every color comes from theme custom properties, and the glass has to stay glassy in both light and dark themes.