Indicator
A dot, count, or short label for drawing attention to state.
Introduction
Indicators mark something as needing attention — unread messages, pending items, a status. They attach to another element rather than standing alone.
This is UX4G's Badge component. IndiaCN's Badge is a label/tag, which is a different thing, so the notification form lives here under its own name to keep both usable.
| Variant | Size | Use |
|---|---|---|
dot | 6px | Something changed; no number needed |
count | 16px circle, grows to a pill | A number of pending items |
text | pill, 20px tall | A one-word status |
Installation
npx shadcn@latest add https://indiacn.in/r/indicator.jsonimport { Indicator } from '@/components/ui/indicator';Usage
<Indicator variant="dot" />
<Indicator variant="count">3</Indicator>
<Indicator theme="success" variant="text">Resolved</Indicator>Examples
Variants
332Primary
import { Bell, Mail } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Indicator } from '@/components/ui/indicator';
import { Body3, Label2 } from '@/components/ui/typography';
export default function Component() {
return (
<div className='flex items-center gap-6'>
<Indicator variant='dot' />
<Indicator variant='count'>3</Indicator>
<Indicator variant='count'>32</Indicator>
<Indicator variant='text'>Primary</Indicator>
</div>
);
}Themes
UX4G defines three states for this component: primary, success and danger.
3Primary
3Success
3Danger
import { Bell, Mail } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Indicator } from '@/components/ui/indicator';
import { Body3, Label2 } from '@/components/ui/typography';
export default function Component() {
return (
<div className='grid gap-4'>
<div className='flex items-center gap-4'>
<Indicator theme='primary' variant='dot' />
<Indicator theme='primary' variant='count'>
3
</Indicator>
<Indicator theme='primary' variant='text'>
Primary
</Indicator>
</div>
<div className='flex items-center gap-4'>
<Indicator theme='success' variant='dot' />
<Indicator theme='success' variant='count'>
3
</Indicator>
<Indicator theme='success' variant='text'>
Success
</Indicator>
</div>
<div className='flex items-center gap-4'>
<Indicator theme='danger' variant='dot' />
<Indicator theme='danger' variant='count'>
3
</Indicator>
<Indicator theme='danger' variant='text'>
Danger
</Indicator>
</div>
</div>
);
}Attached to an Element
Position it with absolute on the indicator and relative on the wrapper.
5
Grievance statusResolved
import { Bell, Mail } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Indicator } from '@/components/ui/indicator';
import { Body3, Label2 } from '@/components/ui/typography';
export default function Component() {
return (
<div className='flex items-center gap-8'>
<div className='relative inline-flex'>
<Button theme='primary' variant='outlined' size='md' iconButton aria-label='Notifications'>
<Bell />
</Button>
<Indicator theme='danger' variant='count' className='absolute -top-1 -right-1'>
5
</Indicator>
</div>
<div className='relative inline-flex'>
<Button theme='primary' variant='outlined' size='md' iconButton aria-label='Inbox'>
<Mail />
</Button>
<Indicator theme='danger' variant='dot' className='absolute top-0 right-0' />
</div>
<div className='flex items-center gap-2'>
<Label2>Grievance status</Label2>
<Indicator theme='success' variant='text'>
Resolved
</Indicator>
</div>
</div>
);
}Counts
11299+
A single digit renders a 16px circle; longer values grow into a pill.
import { Bell, Mail } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Indicator } from '@/components/ui/indicator';
import { Body3, Label2 } from '@/components/ui/typography';
export default function Component() {
return (
<div className='grid gap-3'>
<div className='flex items-center gap-4'>
<Indicator variant='count'>1</Indicator>
<Indicator variant='count'>12</Indicator>
<Indicator variant='count'>99+</Indicator>
</div>
<Body3 className='text-neutral-500'>
A single digit renders a 16px circle; longer values grow into a pill.
</Body3>
</div>
);
}Accessibility
- A
dotcarries no text, so it isrole="presentation"and invisible to screen readers. The meaning must come from its context — give the element it marks an accessible name that includes the state, e.g.aria-label="Inbox, unread messages". - A
countannounces its number, but not what the number means. Label the container:aria-label="5 unread notifications". - Do not rely on colour alone.
dangerandsuccesslook different but read identically to a screen reader, so the surrounding text must carry the distinction.
API Reference
Indicator
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'dot' | 'count' | 'text' | 'count' | Which form to render. dot ignores children |
theme | 'primary' | 'success' | 'danger' | 'primary' | Colour, from UX4G's three states |
children | ReactNode | — | The number or label. Ignored when variant is dot |
className | string | — | Extra Tailwind classes, typically for positioning |