Skip to content
Brok UI

Loading…

No results

Meters

Open source

Single-value readouts of a fraction of a whole — an arc gauge, a circular progress ring, linear/radial usage readouts and a liquid-fill gauge.

Version
v1.1.3
Stability
stable
License
MIT
Related
Gauge
Circular Progress
Usage Meter
Usage Gauge
5 of 5

Liquid Chart — A liquid-fill gauge: an animated wavy waterline rises inside a ring to show a 0-100 value, with a centered percentage and optional caption. CSS-only, no JavaScript.

Preview

A CSS-only liquid-fill gauge. Set the fill with `:value` (0-100), pick a token tone with `:variant` (primary, success, warning, destructive), and label it with `:label` (accessible) and `:caption` (visible). The waterline animates and freezes flat under reduced-motion.

previews.components.chart-liquid.default.blade.php Blade
<div class="space-y-6">
    <div class="flex flex-wrap items-end justify-center gap-8">
        <x-ui.chart-liquid :value="68" variant="primary" :label="__('Storage')" :caption="__('Storage')" />
        <x-ui.chart-liquid :value="42" variant="success" :label="__('Memory')" :caption="__('Memory')" :height="160" />
        <x-ui.chart-liquid :value="91" variant="warning" :label="__('Quota')" :caption="__('Quota')" :height="160" />
    </div>

    <p class="text-sm text-muted-foreground">
        {{ __('A CSS-only liquid-fill gauge. Set the fill with `:value` (0-100), pick a token tone with `:variant` (primary, success, warning, destructive), and label it with `:label` (accessible) and `:caption` (visible). The waterline animates and freezes flat under reduced-motion.') }}
    </p>
</div>

Installation

terminal
php artisan ui:add chart-liquid

Registry contract

php artisan ui:add chart-liquid writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.

  • blade resources/views/components/ui/chart-liquid.blade.php
Registry dependencies
None — installs on its own.
Packages
composer: jml/brok:^0.2

Use with AI

A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.

chart-liquid.md
# Brok UI: Liquid Chart (`chart-liquid`)

A liquid-fill gauge: an animated wavy waterline rises inside a ring to show a 0-100 value, with a centered percentage and optional caption. CSS-only, no JavaScript.

Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.

## Install

```bash
php artisan ui:add chart-liquid
```

## Usage

```blade
<div class="space-y-6">
    <div class="flex flex-wrap items-end justify-center gap-8">
        <x-ui.chart-liquid :value="68" variant="primary" :label="__('Storage')" :caption="__('Storage')" />
        <x-ui.chart-liquid :value="42" variant="success" :label="__('Memory')" :caption="__('Memory')" :height="160" />
        <x-ui.chart-liquid :value="91" variant="warning" :label="__('Quota')" :caption="__('Quota')" :height="160" />
    </div>

    <p class="text-sm text-muted-foreground">
        {{ __('A CSS-only liquid-fill gauge. Set the fill with `:value` (0-100), pick a token tone with `:variant` (primary, success, warning, destructive), and label it with `:label` (accessible) and `:caption` (visible). The waterline animates and freezes flat under reduced-motion.') }}
    </p>
</div>
```

## Props

- `value` (int, default `68`) — Fill level from 0 to 100; clamped into range.
- `variant` (success|warning|destructive, default `primary`) — Tone of the liquid and ring: primary, success, warning, or destructive.
- `label` (mixed|null, default `null`) — Accessible label for the figure; defaults to "Capacity".
- `caption` (mixed|null, default `null`) — Optional small caption rendered under the percentage.
- `height` (int, default `220`) — Rendered height in pixels; the SVG keeps a 1:1 viewBox.

## Use when

- Use when trend, distribution, flow, intensity, or relationship questions are easier to answer visually than in raw numbers.
- Showing a single 0-100 capacity or fill value with a distinctive, eye-catching visual, such as a storage or battery gauge.
- Wanting an animated, dependency-free SVG indicator with no JavaScript or chart library.

## Avoid when

- Do not use a chart when users mainly need exact lookup or when a single metric would be clearer as text or a stat.
- A more conventional dial reading is expected; use gauge or circular-progress instead.
- The value is not a 0-100 fill amount, such as a trend over time; use chart instead.

## Anti-patterns

- Decorative charts without a meaningful question

## Rules

- Use the `<brok:chart-liquid>` tag (or `<x-ui.chart-liquid>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.

## Links

- Docs: https://brokui.dev/docs/components/chart-liquid
- Registry JSON (files, props, contract): https://brokui.dev/r/open/chart-liquid.json

Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.

API

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
Prop Type Default Description
value int 68 Fill level from 0 to 100; clamped into range.
variant success | warning | destructive primary Tone of the liquid and ring: primary, success, warning, or destructive.
label mixed | null null Accessible label for the figure; defaults to "Capacity".
caption mixed | null null Optional small caption rendered under the percentage.
height int 220 Rendered height in pixels; the SVG keeps a 1:1 viewBox.

Slots

  • default — Unused; the figure renders from props only.

Data slots

Stable hooks for CSS overrides and browser tests.

chart-liquid chart-liquid-wave

Behavior

  • Two overlapping wave paths scroll horizontally in a CSS keyframe loop, clipped to an inner circle to read as a rising liquid waterline.
  • The waterline's vertical position maps 0% to the bottom of the circle and 100% to the top.
  • Under prefers-reduced-motion the waves render as a static fill with no animation.
  • Declares registry capability flags: a11y, responsive, rtl, darkMode, localized.

Guidance

Chart and visual analytics

Reveal a visual trend, distribution, flow, or relationship.

Use when

  • Use when trend, distribution, flow, intensity, or relationship questions are easier to answer visually than in raw numbers.
  • Showing a single 0-100 capacity or fill value with a distinctive, eye-catching visual, such as a storage or battery gauge.
  • Wanting an animated, dependency-free SVG indicator with no JavaScript or chart library.

Avoid when

  • Do not use a chart when users mainly need exact lookup or when a single metric would be clearer as text or a stat.
  • A more conventional dial reading is expected; use gauge or circular-progress instead.
  • The value is not a 0-100 fill amount, such as a trend over time; use chart instead.

Use instead

  • Table for exact lookup
  • Stat for a single value

Anti-patterns

  • Decorative charts without a meaningful question
Anatomy
root chart-liquid-wave
Theming hooks
chart-liquid chart-liquid-wave

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
Keyboard
No component-owned keyboard interaction; native element behavior applies.
Focus
none
  • The figure carries role="img" with an aria-label combining the label and value, so a screen reader hears the number even though the SVG is decorative.
  • The inner SVG is aria-hidden since its content is already spoken through the figure's aria-label.
  • Semantic HTML and a stable data-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, so keyboard focus is always visible.
  • Disabled and invalid states are conveyed to assistive tech, not by color alone.
  • Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
  • Labels go through __() and layout uses logical properties (ms-*, text-start), so it mirrors under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Safe

Livewire can update this component through forwarded wire:* attributes.

Source

The exact, editable file ui:add writes into your app. Previews render this same code; there are no preview-only components.

resources/views/components/ui/chart-liquid.blade.php Blade
@props([
    // 0..100 fill level.
    'value' => 68,
    // primary | success | warning | destructive — maps to a token text class.
    'variant' => 'primary',
    // Accessible label for the figure (defaults to "Capacity").
    'label' => null,
    // Optional small caption rendered under the percentage.
    'caption' => null,
    // Rendered height in px (the SVG keeps a 1:1 viewBox).
    'height' => 220,
])

@php
    $styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
    $variant = $styles['normalizeVariant']($variant);
    // ── Clamp + resolve copy ──────────────────────────────────────────────
    $value = max(0, min(100, (int) $value));
    $label ??= __('Capacity');

    // Fixed (non-spliced) variant → token class map. Full strings so the
    // Tailwind JIT can see every utility; never concatenate a variable in.
    $tone = match ($variant) {
        'success' => 'text-success',
        'warning' => 'text-warning',
        'destructive' => 'text-destructive',
        default => 'text-primary',
    };

    // ── Geometry (viewBox 0 0 200 200) ────────────────────────────────────
    $cx = 100;
    $cy = 100;
    $r = 90;                  // outer ring radius
    $top = $cy - $r;          // 10
    $innerH = 2 * $r;         // 180
    // Waterline: 0% → bottom of the circle, 100% → top of the circle.
    $waterline = $top + (1 - $value / 100) * $innerH;

    // Two wave bands, twice as wide as the circle so they can slide -50%
    // (one wavelength) seamlessly. Each is a path of two cosine humps that
    // tile horizontally, capped to a deep rectangle below.
    $waveW = 400;             // 2× viewBox width
    $amp = 6;                 // wave amplitude
    $bottomY = 200;

    $buildWave = static function (float $y, float $amplitude, float $phase) use ($waveW, $bottomY) {
        // Start at the left edge on the waterline, draw two full humps across
        // the doubled width using cubic segments, then close down the sides.
        $half = $waveW / 4;   // quarter wavelength control span
        $d = sprintf('M0 %.2f', $y);
        $x = 0.0;
        for ($i = 0; $i < 4; $i++) {
            $dir = ($i + ($phase > 0 ? 1 : 0)) % 2 === 0 ? -1 : 1;
            $cp = $x + $half / 2;
            $nx = $x + $half;
            $d .= sprintf(' Q%.2f %.2f %.2f %.2f', $cp, $y + $dir * $amplitude, $nx, $y);
            $x = $nx;
        }
        $d .= sprintf(' L%.2f %.2f L0 %.2f Z', $waveW, $bottomY, $bottomY);

        return $d;
    };

    $frontWave = $buildWave($waterline, $amp, 0);
    $backWave = $buildWave($waterline + 2, $amp * 0.7, 1);

    $H = max(120, (int) $height);
    $clipId = 'ui-liquid-clip-' . uniqid();
@endphp

{{-- Keyframes carry NO colour; reduced-motion users get a static fill. --}}
<style>
    @keyframes ui-chart-liquid-wave {
        from { transform: translateX(0); }
        to { transform: translateX(-200px); }
    }
    @media (prefers-reduced-motion: no-preference) {
        [data-slot="chart-liquid-wave"] {
            animation: ui-chart-liquid-wave linear infinite;
            transform-box: view-box;
        }
        [data-slot="chart-liquid-wave"][data-wave="front"] { animation-duration: 4s; }
        [data-slot="chart-liquid-wave"][data-wave="back"] { animation-duration: 6s; }
    }
</style>

<figure
    data-slot="chart-liquid"
    data-variant="{{ $variant }}"
    role="img"
    aria-label="{{ $label }}: {{ $value }}%"
    {{ $attributes->merge(['class' => 'inline-flex flex-col items-center gap-2']) }}
>
    <svg
        viewBox="0 0 200 200"
        width="{{ $H }}"
        height="{{ $H }}"
        preserveAspectRatio="xMidYMid meet"
        class="block h-auto max-w-full {{ $tone }}"
        aria-hidden="true"
    >
        <defs>
            <clipPath id="{{ $clipId }}">
                <circle cx="{{ $cx }}" cy="{{ $cy }}" r="{{ $r - 4 }}" />
            </clipPath>
        </defs>

        {{-- Faint track behind the liquid. --}}
        <circle
            cx="{{ $cx }}"
            cy="{{ $cy }}"
            r="{{ $r - 4 }}"
            fill="currentColor"
            class="opacity-[0.06]"
        />

        {{-- Liquid: both waves clipped to the inner circle. --}}
        <g clip-path="url(#{{ $clipId }})">
            <path
                data-slot="chart-liquid-wave"
                data-wave="back"
                d="{{ $backWave }}"
                fill="currentColor"
                class="opacity-30"
            />
            <path
                data-slot="chart-liquid-wave"
                data-wave="front"
                d="{{ $frontWave }}"
                fill="currentColor"
                class="opacity-70"
            />
        </g>

        {{-- Outer ring in the token colour. --}}
        <circle
            cx="{{ $cx }}"
            cy="{{ $cy }}"
            r="{{ $r }}"
            fill="none"
            stroke="currentColor"
            stroke-width="3"
            class="opacity-40"
        />

        {{-- Centered percentage. --}}
        <text
            x="{{ $cx }}"
            y="{{ $cy }}"
            text-anchor="middle"
            dominant-baseline="central"
            class="fill-foreground text-4xl font-bold tabular-nums"
        >{{ $value }}%</text>
    </svg>

    @if ($caption)
        <figcaption class="text-sm text-muted-foreground">{{ $caption }}</figcaption>
    @endif
</figure>

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.1.3
Status
Stable
License
open
Accessibility reviewed
No review date recorded
Last breaking change
No date recorded
Deprecation
Not deprecated
Contract
v6
Foundation
≥ 1.0.0