Component 33 of 60
Meter
Shows a scalar value within a known range.
Starting the preview. GPUI compiles to WebAssembly, so the first load takes a few seconds. This browser has neither WebGPU nor WebGL 2, so the live preview cannot run. The code on this page still works. The preview did not start.
fn meter_preview(&self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div()
.w_full()
.max_w(px(320.0))
.flex()
.flex_col()
.gap(px(8.0))
.child("Storage used · 68%")
.child(
Meter::new("preview.meter")
.value(68.0)
.aria_label("Storage used: 68 percent"),
)
}
Installation
After the one-time setup, run the command from your app root. The CLI
writes the modules and keeps src/ui/mod.rs in step.
gpuicn add meter
If you prefer to copy the files by hand, declare them yourself:
// src/ui/mod.rs
pub mod theme;
pub mod meter;
// src/main.rs
mod ui;
Usage
Put this in a view module and return example() from its render method.
use crate::ui::meter::Meter;
use gpui_kit::IntoElement;
fn example() -> impl IntoElement {
Meter::new("storage").value(68.).aria_label("Storage used")
}
API reference
pub fn new(id: impl Into<ElementId>) -> Self
Creates a `Meter` with a stable caller-owned ID.
pub fn value(mut self, value: f64) -> Self
Sets the caller-owned value displayed by this control.
pub fn range(mut self, min: f64, max: f64) -> Self
Sets the minimum and maximum for the displayed value.
pub fn aria_label(mut self, label: impl Into<SharedString>) -> Self
Sets the accessible name of the control.
Installed source, 99 lines
//! Nova-styled semantic numeric meter.
use gpui_kit::{
App, ElementId, InteractiveElement as _, IntoElement, ParentElement as _, RenderOnce, Role,
SharedString, StatefulInteractiveElement as _, Styled, Window, div, relative,
};
use super::theme::UiTheme;
#[derive(IntoElement)]
/// A Nova meter for a bounded scalar value.
pub struct Meter {
style: gpui_kit::StyleRefinement,
id: ElementId,
value: f64,
min: f64,
max: f64,
aria_label: Option<SharedString>,
}
impl Meter {
/// Creates a `Meter` with a stable caller-owned ID.
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
style: gpui_kit::StyleRefinement::default(),
id: id.into(),
value: 0.,
min: 0.,
max: 100.,
aria_label: None,
}
}
/// Sets the caller-owned value displayed by this control.
pub fn value(mut self, value: f64) -> Self {
self.value = value;
self
}
/// Sets the minimum and maximum for the displayed value.
pub fn range(mut self, min: f64, max: f64) -> Self {
self.min = min;
self.max = max;
self
}
/// Sets the accessible name of the control.
pub fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
self.aria_label = Some(label.into());
self
}
}
impl RenderOnce for Meter {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = UiTheme::read(cx).clone();
let spacing = theme.spacing.unit;
assert!(
self.value.is_finite()
&& self.min.is_finite()
&& self.max.is_finite()
&& self.min < self.max,
"meter needs a finite value and an increasing finite range"
);
let fraction =
((self.value.clamp(self.min, self.max) - self.min) / (self.max - self.min)) as f32;
let root = div()
.id(self.id)
.role(Role::Meter)
.aria_numeric_value(self.value.clamp(self.min, self.max))
.aria_min_numeric_value(self.min)
.aria_max_numeric_value(self.max)
.w_full();
let mut root = super::theme::apply_style(root, &self.style);
if let Some(label) = self.aria_label {
root = root.aria_label(label);
}
root.child(
div()
.relative()
.w_full()
.h(spacing * 1_f32)
.rounded_full()
.overflow_hidden()
.bg(theme.colors.muted)
.child(
div()
.w(relative(fraction))
.h_full()
.rounded_full()
.bg(theme.colors.primary),
),
)
}
}
impl gpui_kit::Styled for Meter {
fn style(&mut self) -> &mut gpui_kit::StyleRefinement {
&mut self.style
}
}
Platform notes
Visual reference: shadcn/ui 4.19.0, Neutral Nova. Implementation:
crates/ui/registry/meter/meter.rs, using GPUI Kit 0.6.4.
Kit supplies the meter primitive and bounded value presentation. gpuicn applies the Nova track and fill. The application supplies the accessible name and value.
The Rust source and Usage example define the supported API. Browser DOM/CSS behavior, exact exit animations and cross-platform screen-reader parity are not implied by the native component. See migration qualification for the checks completed on this revision.