Component 52 of 60
Tag
A compact chip for a status, role, or removable filter.
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 tag_preview(&self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.flex()
.flex_col()
.items_center()
.gap(px(16.0))
.child(
div()
.flex()
.flex_wrap()
.justify_center()
.gap(px(8.0))
.child(Tag::new("preview.tag.online", "Online").dot(gpui_kit::rgb(0x22c55e)))
.child(Tag::new("preview.tag.away", "Away").dot(gpui_kit::rgb(0xf59e0b)))
.child(
Tag::new("preview.tag.bot", "Bot")
.variant(TagVariant::Outline)
.icon(LucideIcon::Bot),
)
.child(
Tag::new("preview.tag.deactivated", "Deactivated")
.variant(TagVariant::Destructive),
),
)
.when(!self.checked, |root| {
root.child(
Tag::new("preview.tag.filter", "from: @arya")
.variant(TagVariant::Outline)
.on_remove(cx.listener(|this, _, _, cx| {
this.checked = true;
this.demo_action = "Removed filter".into();
cx.notify();
})),
)
})
}
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 tag
If you prefer to copy the files by hand, declare them yourself:
// src/ui/mod.rs
pub mod theme;
pub mod button;
pub mod tag;
// src/main.rs
mod ui;
Usage
Put this in a view module and return example() from its render method.
use crate::ui::tag::*;
use gpui_kit::{IntoElement, rgb};
fn example() -> impl IntoElement {
Tag::new("status.away", "Away")
.dot(rgb(0xf59e0b))
.on_remove(|_, _, _| {})
}
API reference
pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self
Creates a `Tag` with a stable caller-owned ID.
pub fn variant(mut self, variant: TagVariant) -> Self
Selects the Nova color treatment.
pub fn icon(mut self, icon: LucideIcon) -> Self
Adds a leading Lucide icon.
pub fn dot(mut self, color: impl Into<Rgba>) -> Self
Adds a leading status dot in `color`, for presence or state.
pub fn on_remove(
mut self,
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self
Adds a trailing remove button, announced as "Remove" plus the label.
Installed source, 163 lines
//! Nova-styled tags for statuses, roles and removable filters.
use std::rc::Rc;
use gpui_icons::{LucideIcon, lucide};
use gpui_kit::{
App, ClickEvent, ElementId, FontWeight, InteractiveElement as _, IntoElement, ParentElement,
RenderOnce, Rgba, SharedString, Styled, Window, div, prelude::FluentBuilder as _, relative,
};
use super::button::{Button, ButtonSize, ButtonVariant};
use super::theme::UiTheme;
type RemoveHandler = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
/// Nova tag color treatments.
pub enum TagVariant {
#[default]
/// Muted fill with a hairline border.
Secondary,
/// Transparent with a border.
Outline,
/// Tinted destructive treatment.
Destructive,
}
/// A compact rectangular chip for a status, role or filter.
#[derive(IntoElement)]
pub struct Tag {
style: gpui_kit::StyleRefinement,
id: ElementId,
label: SharedString,
variant: TagVariant,
icon: Option<LucideIcon>,
dot: Option<Rgba>,
on_remove: Option<RemoveHandler>,
}
impl Tag {
/// Creates a `Tag` with a stable caller-owned ID.
pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
Self {
style: gpui_kit::StyleRefinement::default(),
id: id.into(),
label: label.into(),
variant: TagVariant::Secondary,
icon: None,
dot: None,
on_remove: None,
}
}
/// Selects the Nova color treatment.
pub fn variant(mut self, variant: TagVariant) -> Self {
self.variant = variant;
self
}
/// Adds a leading Lucide icon.
pub fn icon(mut self, icon: LucideIcon) -> Self {
self.icon = Some(icon);
self
}
/// Adds a leading status dot in `color`, for presence or state.
pub fn dot(mut self, color: impl Into<Rgba>) -> Self {
self.dot = Some(color.into());
self
}
/// Adds a trailing remove button, announced as "Remove" plus the label.
pub fn on_remove(
mut self,
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self {
self.on_remove = Some(Rc::new(handler));
self
}
}
impl RenderOnce for Tag {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = UiTheme::read(cx).clone();
let colors = theme.colors;
let (background, foreground, border) = match self.variant {
TagVariant::Secondary => (colors.muted, colors.foreground, colors.border),
TagVariant::Outline => (
colors.background.opacity(0.),
colors.foreground,
colors.border,
),
TagVariant::Destructive => (
colors.destructive.opacity(0.1),
colors.destructive,
colors.destructive.opacity(0.2),
),
};
let remove_label: SharedString = format!("Remove {}", self.label).into();
let root = div()
.id(self.id.clone())
.flex()
.flex_shrink_0()
.items_center()
.gap(theme.space(1.5))
.h(theme.space(6.))
.px(theme.space(2.))
.when(self.on_remove.is_some(), |root| root.pr(theme.space(1.)))
.rounded(theme.radius.md)
.border_1()
.border_color(border)
.bg(background)
.text_color(foreground)
.font_family(theme.fonts.body.clone())
.font_weight(FontWeight::MEDIUM)
.text_size(theme.text(12.))
.line_height(relative(1.))
.whitespace_nowrap()
.when_some(self.dot, |root, color| {
root.child(
div()
.size(theme.space(1.5))
.flex_shrink_0()
.rounded_full()
.bg(color),
)
})
.when_some(self.icon, |root, icon| {
root.child(
lucide(icon)
.size(theme.space(3.5))
.flex_shrink_0()
.text_color(foreground),
)
})
.child(self.label)
.when_some(self.on_remove, |root, handler| {
root.child(
Button::new((self.id, "remove"))
.variant(ButtonVariant::Ghost)
.size(ButtonSize::IconXs)
.w(theme.space(4.))
.h(theme.space(4.))
.rounded(theme.radius.sm)
.aria_label(remove_label)
.on_click(move |event, window, cx| handler(event, window, cx))
.child(
lucide(LucideIcon::X)
.size(theme.space(3.))
.text_color(foreground),
),
)
});
super::theme::apply_style(root, &self.style)
}
}
impl Styled for Tag {
fn style(&mut self) -> &mut gpui_kit::StyleRefinement {
&mut self.style
}
}
Platform notes
shadcn/ui has no Tag. gpuicn's Tag follows GPUI Kit 0.8.1's Tag role, drawn
with Nova tokens. Implementation: crates/ui/registry/tag/tag.rs.
A tag is a 24px rectangular chip with the medium radius, a hairline border and
12px medium text, which sets it apart from the pill-shaped Badge. Secondary,
Outline and Destructive treatments come from the theme. Tag::dot adds a status
dot in an application color, for presence such as online or away; Tag::icon
adds a Lucide icon.
Tag::on_remove adds a ghost icon button that takes keyboard focus and is
announced as "Remove" plus the label. The application removes the tag from its
own state in the handler.