React Clean UI
Surface

Badge

A small pill for counts, statuses, and short labels, optionally clickable.

Badge is a compact, themed label built on Roblox's native ImageLabel. It shows a count, status, or short piece of text, optionally preceded by an icon, and sizes itself to its content. It is non-interactive unless you give it onClick.

Import

import { Badge } from "@rbxts/react-clean-ui";

Basic usage

Loom Previewv0.13.2

The text prop is required and renders the label.

<Badge text="New" />
<Badge text="12" intent="danger" />

Intents

Set intent to control the badge's background, border, and text/icon colors. When intent is omitted, primary is used.

<Badge text="Primary" intent="primary" />
<Badge text="Secondary" intent="secondary" />
<Badge text="Success" intent="success" />
<Badge text="Info" intent="info" />
<Badge text="Warning" intent="warning" />
<Badge text="Danger" intent="danger" />

In the default theme, primary is a neutral light-grey pill with dark text, while the other intents use solid intent-colored backgrounds with white text.

Icons

Set icon to draw an icon before the text. By default the icon is tinted with the same color as the text; set iconColor to tint it separately (see Custom colors).

Loom Previewv0.13.2

The gap between the icon and the text is half of the resolved spacing at the badge's scale, rounded up, the same gap HStack uses by default.

Scaling

Badge supports the shared scale prop, which moves the text size, icon size, padding, and icon/text gap together.

Loom Previewv0.13.2

Available scales are xs, sm, md, lg, and xl. When scale is not provided, the component uses theme.default.scale.

The Wooden theme uses the same text style (PatrickHand, Size18, regular) and the same "2px 10px" padding at every scale, so Wooden badges look the same size whatever scale you pass. Only the icon size and the icon/text gap still follow scale.

Placing a badge

Badge sizes to its content and never stretches to fill its parent, so it sits naturally next to other content in a stack. Use LayoutOrder to order it inside a layout.

Loom Previewv0.13.2

Outside a layout, set Position and AnchorPoint to place the badge yourself.

<Badge
    text="Live"
    intent="danger"
    scale="xs"
    Position={new UDim2(1, -4, 0, 4)}
    AnchorPoint={new Vector2(1, 0)}
/>

Custom colors

Each color can be overridden on a single badge. Anything you don't set falls back to the intent's theme value.

  • BackgroundColor3 and BackgroundTransparency set the fill.
  • textColor sets the text color.
  • iconColor sets the icon color. When unset, the icon uses the resolved text color, so textColor alone tints both.
  • borderColor, borderThickness, borderTransparency, and borderPosition ("inner" or "outer", default "inner") shape the border. borderThickness falls back to the theme's badge.borderThickness, and the border only renders when the resolved thickness is greater than 0.
  • padding overrides the theme padding at the badge's scale, per side, in CSS shorthand.
Loom Previewv0.13.2

The first row overrides colors, border, and padding per instance. The second row has clickable badges, described next: "Likes" uses the theme's hover/press shading, and "Follow" sets hoverStyle/pressStyle.

Clickable badges

Pass onClick to make the badge clickable. The whole pill is the click target.

import React, { useState } from "@rbxts/react";
import { Badge } from "@rbxts/react-clean-ui";

export function LikeBadge() {
    const [likes, setLikes] = useState(0);

    return (
        <Badge
            text={`Likes ${likes}`}
            icon="thumbs-up"
            intent="info"
            onClick={() => setLikes(likes + 1)}
        />
    );
}

The badge is hovered while the pointer is over it or it holds gamepad selection, and pressed while the left mouse button or a touch is held down on it. By default the background is shaded in these states using the theme's badge.hoverDarken and badge.pressDarken (see Theme values). There is no disabled state.

Use hoverStyle and pressStyle to pick the colors yourself. Both take a BadgeStateStyle, with optional backgroundColor, backgroundTransparency, textColor, iconColor, and borderColor.

<Badge
    text="Follow"
    intent="primary"
    hoverStyle={{ backgroundColor: Color3.fromHex("#2E9D63"), textColor: new Color3(1, 1, 1) }}
    pressStyle={{ backgroundColor: Color3.fromHex("#1F6E45") }}
    onClick={() => print("Followed")}
/>

While pressed, pressStyle is layered over hoverStyle, so a field you only set in hoverStyle (like textColor above) still applies while pressed. Each color resolves, highest priority first:

  • Hovered: hoverStyle, then the instance prop (for example textColor), then the intent value.
  • Pressed: pressStyle, then hoverStyle, then the instance prop, then the intent value.

When neither style sets backgroundColor, the resting background is shaded by the theme instead. hoverStyle and pressStyle are ignored unless onClick is set.

Theming

theme.components.badge sets the look of every badge. Use createTheme (or extendTheme) to change it theme-wide, then apply it with ThemeProvider.

import { Badge, createTheme, ThemeProvider } from "@rbxts/react-clean-ui";

const themeWithSquareBadges = createTheme({
    components: {
        badge: {
            cornerRadius: "4px",
            borderThickness: 0,
            padding: "2px 8px",
            intents: {
                danger: {
                    default: {
                        textColor: Color3.fromHex("#FFFFFF"),
                        backgroundColor: Color3.fromHex("#B91C1C"),
                        borderColor: Color3.fromHex("#B91C1C"),
                    },
                },
            },
        },
    },
});

<ThemeProvider theme={themeWithSquareBadges}>
    <Badge text="Offline" intent="danger" />
</ThemeProvider>

The default theme's badge.typography is keyed by scale (xs–xl), and themes are deep-merged. If you override it with a plain object such as { size: Enum.FontSize.Size14 }, the scale keys survive the merge and your plain fields are ignored. Override typography per scale key instead, as the Wooden theme does.

Set backgroundColor, borderColor, and textColor for every intent you customize. The component's primary entry is layered over each intent's theme colors, so any field left unset on an intent can inherit primary's neutral pill colors.

Props

Badge-specific props

PropTypeDefaultDescription
textstringRequiredThe label shown in the badge. Never wraps.
iconIconNameundefinedRenders an icon before the text, tinted with iconColor or the text color.
textColorColor3undefinedText color. Unset uses the intent's text color.
iconColorColor3undefinedIcon color. Unset uses the resolved text color.
paddingCssPaddingundefinedPer-side padding override over the theme padding at the badge's scale.
onClick() => voidundefinedMakes the badge clickable and calls this when it is activated.
hoverStyleBadgeStateStyleundefinedColors used while hovered. Only used with onClick.
pressStyleBadgeStateStyleundefinedColors used while pressed, layered over hoverStyle. Only used with onClick.
namestring"Badge"Name assigned to the underlying ImageLabel.
LayoutOrdernumberundefinedNative Roblox layout order.
PositionUDim2undefinedNative Roblox position.
AnchorPointVector2undefinedNative Roblox anchor point.

BadgeStateStyle is exported. Its fields are all optional: backgroundColor, textColor, iconColor, and borderColor (Color3), and backgroundTransparency (number).

Shared props

The badge also supports props inherited from the following interfaces.

InterfacePurpose
IntentElementPropsConfigures the badge's color scheme. Defaults to primary.
ScalableElementPropsConfigures text size, icon size, padding, and icon/text gap together.
IconElementPropsConfigures the icon prop's accepted values.
BackgroundElementPropsConfigures BackgroundColor3 and BackgroundTransparency, overriding the intent's background. Transparency falls back to the intent's value, then 0.
BorderElementPropsConfigures borderColor, borderThickness, borderTransparency, and borderPosition ("inner" by default).

Behaviour

  • The badge sizes to its content (AutomaticSize.XY with a zero base size) and never stretches to its parent's width.
  • Without onClick, the badge never takes input: its ImageLabel has Active and Selectable set to false, and no events are wired.
  • With onClick, the root ImageLabel keeps the background, corners, border, and gradient, and its content moves into a transparent, content-sized ImageButton child named "BadgeButton". The root sizes to that button, so the whole pill is clickable.
  • Hover and press colors never use the theme's button hover colors. They come only from hoverStyle/pressStyle and the badge's own hoverDarken/pressDarken shading. Intent colors always resolve from the intent's default state.
  • Gamepad selection shows the hover state, but a gamepad button press does not show the pressed visual. Only a held mouse button or touch does. Activating with a gamepad still calls onClick.
  • There is no disabled state.
  • Removing onClick clears the hover and press state, so adding it back doesn't show a stale state.
  • Content is laid out left to right and vertically centered, without wrapping: the optional icon first, then the text. Text wrapping is disabled.
  • The border stroke only renders when the resolved borderThickness (prop, then theme) is greater than 0.
  • backgroundImage and backgroundGradient always come from the theme intent; there are no instance props for them.
  • When the resolved intent has no backgroundImage, the image is cleared (Image=""), so a flat badge never shows a stale image.
  • The ref forwards to the root ImageLabel, whether or not the badge is clickable.

Theme values

The badge uses values from theme.components.badge for:

  • cornerRadius — the corner radius ("50%" for a pill in the default and dark themes, "4px" in Sandstone and Wooden)
  • borderThickness — the inner border thickness; 0 renders no border
  • spacing / padding (optional) — internal padding, resolved per badge scale (not theme.default.spacing). The default theme uses per-scale padding from "1px 4px" (xs) to "6px 14px" (xl)
  • typography (optional) — a plain partial typography style or a per-scale one, merged over the theme typography for the badge's scale. The default theme uses bold text from Size10 (xs) to Size24 (xl), with no font so each theme keeps its own
  • intents (optional) — per-intent textColor, backgroundColor, borderColor, backgroundTransparency, backgroundImage, and backgroundGradient, layered on top of theme.colors.intents
  • hoverDarken / pressDarken (optional) — how much a clickable badge's resting background is shaded while hovered or pressed, when hoverStyle/pressStyle set no backgroundColor. Positive values lerp toward black, negative values lerp toward white by their absolute value. pressDarken falls back to hoverDarken; unset or 0 means no shading. Default and Sandstone use 0.08 / 0.16, Dark uses -0.1 / -0.2 (lightening its dark backgrounds), and Wooden inherits Default's values

These values can be changed by providing a custom Clean UI theme.

GitHub Repository

On this page