Label source
Inspect and copy the reviewed source included with this Free component.
Free source · 2 files
mod.rs
// SPDX-FileCopyrightText: 2026 Proa Labs, Inc.
// SPDX-License-Identifier: (MIT OR Apache-2.0) AND MIT
//! Label component - accessible form label
//!
//! A simple styled label component that renders as a native HTML `<label>` element.
//! Based on shadcn/ui's Label component.
mod md;
use crate::tw_join;
use proa_core::{ClassList, WebContext, WebRenderSync, WriteBuf};
use proa_macros::html_sync;
/// Base styles for the label - matches shadcn/ui exactly
/// "flex items-center gap-2 text-sm leading-none font-medium select-none
/// group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50
/// peer-disabled:cursor-not-allowed peer-disabled:opacity-50"
const LABEL_BASE: &str = tw_join!(
"flex",
"items-center",
"gap-2",
"text-sm",
"leading-none",
"font-medium",
"text-foreground",
"select-none",
"group-data-[disabled=true]:pointer-events-none",
"group-data-[disabled=true]:opacity-50",
"peer-disabled:cursor-not-allowed",
"peer-disabled:opacity-50"
);
/// Type-safe Label component.
///
/// Renders an accessible label associated with form controls.
/// Uses the native HTML `<label>` element for proper accessibility.
///
/// # Example
///
/// ```ignore
/// use proa_ui::Label;
/// use proa_macros::html_sync;
///
/// // Basic usage with for attribute
/// html_sync! {
/// <div class="flex items-center gap-2">
/// <input type="checkbox" id="terms" />
/// {Label {
/// for_id: Some("terms"),
/// text: "Accept terms and conditions",
/// ..Label::new()
/// }}
/// </div>
/// }.render(cx);
///
/// // With custom class
/// html_sync! {
/// {Label {
/// for_id: Some("email"),
/// text: "Email address",
/// class: Some("text-red-500"),
/// ..Label::new()
/// }}
/// }.render(cx);
/// ```
pub struct Label {
/// The id of the form element this label is for
pub for_id: Option<&'static str>,
/// The text content of the label
pub text: &'static str,
/// Additional CSS classes
pub class: Option<&'static str>,
/// Custom id for the label element
pub id: Option<&'static str>,
}
impl Label {
/// Create a new label with default settings
pub const fn new() -> Self {
Self {
for_id: None,
text: "",
class: None,
id: None,
}
}
/// Create a new label with text
pub const fn with_text(text: &'static str) -> Self {
Self {
for_id: None,
text,
class: None,
id: None,
}
}
}
impl Default for Label {
fn default() -> Self {
Self::new()
}
}
/// Get the label classes
#[inline(always)]
pub fn label_classes() -> ClassList<2> {
ClassList::new().add(LABEL_BASE)
}
impl<B: WriteBuf, L: ::proa_core::DataLoader> WebRenderSync<L, B> for Label {
#[inline(always)]
fn render(
self,
cx: &mut WebContext<L, B>,
) -> Result<(), proa_core::shared::write_buf::WriteError> {
let classes = label_classes().add_opt(self.class);
html_sync! {
<label data-label class={classes} for={self.for_id} id={self.id} data-slot="label">
{self.text}
</label>
}
.render(cx)
}
}
pub use Label as LabelComponent;
#[cfg(test)]
mod tests {
use super::*;
use proa_core::ctx::Ctx;
#[test]
fn label_renders_with_defaults() {
let mut out = Vec::new();
let mut cx = Ctx::with_buffer(&mut out);
Label {
text: "Email",
..Label::new()
}
.render(&mut cx)
.unwrap();
let html = String::from_utf8(out).unwrap();
assert!(html.contains("<label"));
assert!(html.contains("Email"));
assert!(html.contains("data-slot=\"label\""));
assert!(html.contains("text-sm"));
assert!(html.contains("font-medium"));
}
#[test]
fn label_renders_with_for_attribute() {
let mut out = Vec::new();
let mut cx = Ctx::with_buffer(&mut out);
Label {
for_id: Some("my-input"),
text: "Username",
..Label::new()
}
.render(&mut cx)
.unwrap();
let html = String::from_utf8(out).unwrap();
assert!(html.contains("for=\"my-input\""));
assert!(html.contains("Username"));
}
#[test]
fn label_renders_with_custom_class() {
let mut out = Vec::new();
let mut cx = Ctx::with_buffer(&mut out);
Label {
text: "Custom",
class: Some("text-red-500"),
..Label::new()
}
.render(&mut cx)
.unwrap();
let html = String::from_utf8(out).unwrap();
assert!(html.contains("text-red-500"));
}
#[test]
fn label_renders_with_id() {
let mut out = Vec::new();
let mut cx = Ctx::with_buffer(&mut out);
Label {
id: Some("my-label"),
text: "Test",
..Label::new()
}
.render(&mut cx)
.unwrap();
let html = String::from_utf8(out).unwrap();
assert!(html.contains("id=\"my-label\""));
}
#[test]
fn label_with_text_constructor() {
let mut out = Vec::new();
let mut cx = Ctx::with_buffer(&mut out);
Label::with_text("Quick label").render(&mut cx).unwrap();
let html = String::from_utf8(out).unwrap();
assert!(html.contains("Quick label"));
}
#[test]
fn label_size_is_reasonable() {
assert!(std::mem::size_of::<Label>() < 128);
const _LABEL: Label = Label::new();
}
}
FreeLabel
Accessible form labels.
Install with the Proa CLI
proa ui add labelInstalls the reviewed component source, dependencies, shared support files, and required legal notices.
CLI and registry setup