Uppercase Oswald badge in gold, red, neutral and subtle tones, plus a ribbon badge with a leading mark. Ships in the Neoconstruct theme.
import * as React from "react"
import { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import {
Marker,
type MarkerProp,
} from "@/components/neoconstruct/neoconstruct-marker"
const badgeVariants = cva(
"inline-flex w-fit shrink-0 items-center justify-center gap-1.5 overflow-hidden border-2 border-border px-3 py-1 font-heading text-xs tracking-wider whitespace-nowrap uppercase focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [&>svg]:pointer-events-none [&>svg]:size-3",
{
variants: {
/**
* The colors. `default` (the default), `outline` and `warning` are gold
* with dark text. `urgent` and `error` are poster red with cream text.
* `neutral` uses `secondary-background`, warm white in light mode.
* `subtle` is a 5% tint of the text color, with a transparent border
* and text at 75%. `success` fills with the text color and sets the
* text in the background color.
*/
variant: {
default: "bg-accent text-accent-foreground",
outline: "bg-accent text-accent-foreground",
urgent: "bg-main text-main-foreground",
neutral: "bg-secondary-background text-foreground",
subtle: "border-transparent bg-foreground/5 text-foreground/75",
success: "bg-foreground text-background",
warning: "bg-accent text-accent-foreground",
error: "bg-main text-main-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
/**
* A small Neoconstruct label for a status or category: uppercase text in the
* heading font inside a 2px border. Pick its colors with `variant`. It
* carries the Neoconstruct theme with it, wherever it is placed.
*
* It renders a `span`; pass `render` to use another element, such as a link.
*/
function Badge({
className,
variant,
render,
...props
}: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
return useRender({
defaultTagName: "span",
render,
props: mergeProps<"span">(
{
"data-slot": "badge",
"data-theme": "neoconstruct",
className: cn(badgeVariants({ variant }), className),
} as React.ComponentProps<"span">,
props
),
})
}
/**
* An urgent Neoconstruct badge: an uppercase label on the `main` color
* (poster red by default), with a mark before the text and a gold triangle
* in the top-right corner.
*/
function RibbonBadge({
className,
children,
marker = "band",
...props
}: React.ComponentProps<"span"> & {
/** The mark before the text; "none" removes it. Defaults to "band". */
marker?: MarkerProp
}) {
return (
<span
data-slot="ribbon-badge"
data-theme="neoconstruct"
className={cn(
"relative inline-flex items-center justify-center",
"border-2 border-border",
"bg-main text-main-foreground",
"px-4 py-1",
"font-heading text-xs tracking-wider uppercase",
"gap-2",
className
)}
{...props}
>
{/* Gold triangle accent in top-right corner */}
<div
aria-hidden="true"
className="absolute top-0 right-0 h-0 w-0"
style={{
borderTop: "14px solid var(--accent)",
borderLeft: "14px solid transparent",
}}
/>
<Marker variant={marker} className="size-3" />
{children}
</span>
)
}
export { Badge, RibbonBadge, badgeVariants }
pnpm dlx shadcn add @01orbital/neoconstruct-badgenpx shadcn add @01orbital/neoconstruct-badgeyarn shadcn add @01orbital/neoconstruct-badgebunx --bun shadcn add @01orbital/neoconstruct-badgeFree under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.
It lands at components/neoconstruct/neoconstruct-badge.tsx, as source you own and edit.
import {
Badge,
RibbonBadge,
} from "@/components/neoconstruct/neoconstruct-badge"
export default function BadgeDemo() {
return (
<div className="flex flex-wrap items-center gap-3">
{/* Default - gold background */}
<Badge variant="default">Default</Badge>
{/* Outline - transparent with border */}
<Badge variant="outline">Beta</Badge>
{/* Ribbon badges with gold accent */}
<RibbonBadge>New</RibbonBadge>
<RibbonBadge>Sold out</RibbonBadge>
</div>
)
}Neoconstruct Badge is built from 2 parts. In the example they nest like this:
Badge
RibbonBadgeBadge: A small Neoconstruct label for a status or category: uppercase text in the heading font inside a 2px border. Pick its colors with variant. It carries the Neoconstruct theme with it, wherever it is placed.RibbonBadge: An urgent Neoconstruct badge: an uppercase label on the main color (poster red by default), with a mark before the text and a gold triangle in the top-right corner.A small Neoconstruct label for a status or category: uppercase text in the heading font inside a 2px border. Pick its colors with variant. It carries the Neoconstruct theme with it, wherever it is placed.
It renders a span; pass render to use another element, such as a link.
Prop
Type
It also accepts the props of the Base UI part it wraps, and the HTML attributes of the element it renders.
An urgent Neoconstruct badge: an uppercase label on the main color (poster red by default), with a mark before the text and a gold triangle in the top-right corner.
Prop
Type
It also accepts the HTML attributes of the element it renders.
npm: @base-ui/react, class-variance-authority, cn. The CLI installs them for you.
Registry pieces it installs with it:
font-unboundedCompare
Every Neoconstruct component side by side: whether it follows the theme or stays dark, how many parts and custom props it has, and whether it is free. Open one for its preview, API and install.
Neoconstruct Button
Neoconstruct button: hard offset shadow that presses in on hover, default, neutral, outline, ghost and reverse variants, optional mark, arrows, rotation and stripe, plus a ribbon button. Ships in the Neoconstruct theme.