Badge, from the Building blocks group of the nertia component library.
npx shadcn@latest add https://ner.sh/r/block-badge.jsonThe recipe carries this block's layout and variant; the install brings the component.
{
"registries": {
"@nertia": "https://ner.sh/r/{name}.json"
}
}npx shadcn@latest add @nertia/block-badgenpm create nersh@latest my-site -- --site https://nertia.ai/components/building-blocks/element-block-badge/site.json scaffolds a project from it.{
"slug": "element-block-badge",
"mode": "builder",
"status": "draft",
"skinId": "zinc",
"composition": {
"id": "element-block-badge",
"sections": [
{
"id": "block-badge",
"instanceId": "stage-block-badge"
}
]
},
"copy": {
"stage-block-badge.text": "Badge",
"stage-block-badge.href": "#",
"stage-block-badge.style": "",
"stage-block-badge.placement": ""
}
}
import type { Section } from '../types';
import { Component } from './Component';
import { schema } from './schema';
import { meta } from './meta';
import { writeCopy } from './writeCopy';
export const blockBadge: Section = { meta, schema, Component, writeCopy };
import type { SectionProps } from '../types';
import { Editable } from '@/components/site/EditableText';
import { compositionEditPath } from '@/lib/composition/compositionEditableCopy';
import { Badge, Icon } from '@/components/foundation';
export function Component({ copy, instanceId }: SectionProps) {
const edit = (slotKey: string) => compositionEditPath(instanceId, slotKey);
const text = copy.text || 'Badge';
const href = copy.href?.trim();
const markedText = <Editable path={edit('text')!}>{text}</Editable>;
// `placement: 'edge'` asks the card around the badge to pin it on its top edge (the
// "Most popular" pill over a featured pricing tier), out of the card's flow so the
// featured card's name and price line up with its neighbours'. block-card does the
// pinning; outside a card the attribute is inert and the badge stays inline.
const placement = (copy.placement ?? '').trim().toLowerCase() === 'edge' ? 'edge' : undefined;
// `style: 'check'` reproduces the monolith's trust-badge row: no pill chrome, a leading
// check mark and the text (`marketing-hero/Component.tsx`'s `trustBadges`).
if (copy.style === 'check') {
const content = (
<span className="inline-flex items-center gap-1.5 text-foreground">
<Icon name="check" className="size-4" />
{markedText}
</span>
);
return (
<div data-section="block-badge" data-placement={placement}>
{href ? <a href={href}>{content}</a> : content}
</div>
);
}
return (
<div data-section="block-badge" data-placement={placement} className={placement ? 'whitespace-nowrap' : undefined}>
{href ? <Badge href={href}>{markedText}</Badge> : <Badge>{markedText}</Badge>}
</div>
);
}
import type { SectionCopySchemaField } from '../types';
export const schema: SectionCopySchemaField[] = [
{ key: 'text', label: 'Text', type: 'text', placeholder: 'Badge', maxLength: 32 },
{ key: 'href', label: 'Link (optional)', type: 'link', placeholder: 'https://' },
{
key: 'style',
label: 'Style',
type: 'text',
placeholder: '',
advanced: true,
options: [
{ value: '', label: 'Pill' },
{ value: 'check', label: 'Check' },
],
},
{
key: 'placement',
label: 'Placement',
type: 'text',
placeholder: '',
maxLength: 4,
advanced: true,
helpText: 'Card edge pins the badge to the top edge of the card, floating outside it. Inline keeps it in the normal flow of content.',
options: [
{ value: '', label: 'Inline' },
{ value: 'edge', label: 'Card edge' },
],
},
];
import type { SectionMeta } from '../types';
export const meta: SectionMeta = {
id: 'block-badge',
displayName: 'Badge',
family: 'component',
tags: ['component', 'atom', 'badge', 'chip', 'proof'],
bucket: 'atom',
};
import type { BrandContext } from '@/lib/brandContext';
export function writeCopy(_ctx: BrandContext) {
return { text: 'Badge' };
}