IndicatorBadge

The IndicatorBadge overlays a visual indicator (badge with count or dot) at a corner of wrapped content to communicate notifications alerts.

Quick Start

Installation
npm install @adaptavant/eds-core
Import
import { IndicatorBadge } from '@adaptavant/eds-core';

Basic Usage

Use IndicatorBadge to overlay indicators (counts or dots) on interactive elements such as an IconButton. The label prop is required to describe the indicator for screen readers.

NOTE: The count is displayed only when it’s greater than 0, and it shows 99+ when the count exceeds 99.

5 unread notifications
More than 99 unread notifications
<Box className="flex items-center gap-6">
  <IndicatorBadge
    count={5}
    label="5 unread notifications"
  >
    {({ childProps }) => (
      <IconButton
        icon={NotificationsIcon}
        aria-label="notifications"
        {...childProps} // must spread childProps onto the wrapped element.
      />
    )}
  </IndicatorBadge>

  {/* In this example, the count exceeds 99. */}
  <IndicatorBadge
    count={150}
    label="More than 99 unread notifications"
  >
    {({ childProps }) => (
      <IconButton
        icon={ChatIcon}
        aria-label="messages"
        {...childProps}
      />
    )}
  </IndicatorBadge>
</Box>

Size

The size prop controls the badge dimensions. When hasDot is true, the size is ignored because dots use a fixed size.

5 unread notifications
5 unread notifications
<Box className="flex items-center gap-6">
  <IndicatorBadge 
    count={5} 
    size="small"
    label="5 unread notifications"
  >
    {({ childProps }) => (
      <IconButton 
        icon={NotificationsIcon} 
        aria-label="notifications" 
        {...childProps}
      />
    )}
  </IndicatorBadge>
  <IndicatorBadge 
    count={5} 
    size="standard"
    label="5 unread notifications"
  >
    {({ childProps }) => (
      <IconButton 
        icon={NotificationsIcon} 
        aria-label="notifications" 
        {...childProps}
      />
    )}
  </IndicatorBadge>

</Box>

Variant

IndicatorBadge uses the same variants as Badge (neutral, positive, critical, caution) with Primary (solid) and Secondary (light) options.

9 urgent notifications
5 unread conversations
<Box className="flex items-center gap-4">
  <IndicatorBadge 
    count={9} 
    variant="criticalPrimary"
    label="9 urgent notifications"
  >
    {({ childProps }) => (
      <IconButton 
        icon={NotificationsIcon} 
        aria-label="notifications" 
        {...childProps}
      />
    )}
  </IndicatorBadge>

  <IndicatorBadge 
    count={5} 
    variant="criticalSecondary"
    label="5 unread conversations"
  >
    {({ childProps }) => (
      <IconButton 
        icon={ChatIcon} 
        aria-label="tasks" 
        {...childProps}
      />
    )}
  </IndicatorBadge>
</Box>

These two show the critical variants; see BadgeVariants for all available options.

Placement

Position the indicator at any of the four corners using the placement prop.

30 unread messages
topLeft
30 unread messages
topRight (default)
30 unread messages
bottomLeft
30 unread messages
bottomRight
<Box className="flex items-center gap-8">
  <Box className="flex flex-col items-center gap-2">
    <IndicatorBadge 
      count={30} 
      placement="topLeft"
      label="30 unread messages"
    >
      {({ childProps }) => (
        <IconButton 
          icon={ChatIcon} 
          aria-label="messages" 
          {...childProps}
        />
      )}
    </IndicatorBadge>
    <Text className="text-body-12">topLeft</Text>
  </Box>

  <Box className="flex flex-col items-center gap-2">
    <IndicatorBadge 
      count={30} 
      placement="topRight"
      label="30 unread messages"
    >
      {({ childProps }) => (
        <IconButton 
          icon={ChatIcon} 
          aria-label="messages" 
          {...childProps}
        />
      )}
    </IndicatorBadge>
    <Text className="text-body-12">topRight (default)</Text>
  </Box>

  <Box className="flex flex-col items-center gap-2">
    <IndicatorBadge 
      count={30} 
      placement="bottomLeft"
      label="30 unread messages"
    >
      {({ childProps }) => (
        <IconButton 
          icon={ChatIcon} 
          aria-label="messages" 
          {...childProps}
        />
      )}
    </IndicatorBadge>
    <Text className="text-body-12">bottomLeft</Text>
  </Box>

  <Box className="flex flex-col items-center gap-2">
    <IndicatorBadge 
      count={30} 
      placement="bottomRight"
      label="30 unread messages"
    >
      {({ childProps }) => (
        <IconButton 
          icon={ChatIcon} 
          aria-label="messages" 
          {...childProps}
        />
      )}
    </IndicatorBadge>
    <Text className="text-body-12">bottomRight</Text>
  </Box>
</Box>

Visibility Control​

Use isVisible to control whether the indicator appears. When isVisible={false}, the count or dot is hidden, but the child element still renders normally.

5 unread notifications
Toggle isVisible prop
const [isVisible, setIsVisible] = React.useState('true');

return (
  <Box className="flex flex-col items-center gap-4">
    <IndicatorBadge
      count={5}
      isVisible={isVisible === 'true'}
      label="5 unread notifications"
    >
      {({ childProps }) => (
        <IconButton
          icon={NotificationsIcon}
          aria-label="notifications"
          {...childProps}
        />
      )}
    </IndicatorBadge>

    <RadioGroup
      legend="Toggle isVisible prop"
      name="visibility"
      onChange={setIsVisible}
      value={isVisible}
    >
      <Radio label="true" value="true" />
      <Radio label="false" value="false" />
    </RadioGroup>
  </Box>
);

Dot Indicator​

Set hasDot={true} to display a simple dot indicator instead of a badge with a count. When hasDot is true, it takes priority over any count value; even if a count is provided, only the dot will be displayed.

Has new updates
<IndicatorBadge 
  hasDot 
  variant="criticalPrimary"
  label="Has new updates"
>
  {({ childProps }) => (
    <IconButton 
      icon={NotificationsIcon} 
      aria-label="notifications" 
      {...childProps}
    />
  )}
</IndicatorBadge>

With NavigationBar

IndicatorBadge integrates seamlessly with the NavigationBar to show notification counts on navigation items. Below is an example of that use case.

<NavigationBar>
  <NavigationBarItem
    href="#calendar"
    icon={() => <CalDynamicIcon>{new Date().getDate()}</CalDynamicIcon>}
    label="Calendar"
  />
  <NavigationBarItem
    href="#services"
    icon={() => (
      <IndicatorBadge
        count={12}
        label="12 unread notifications"
        variant="criticalPrimary"
      >
        {({ childProps }) => <ServicesIcon {...childProps} />}
      </IndicatorBadge>
    )}
    label="Services"
  />
  <NavigationBarItem
    href="#customers"
    icon={CustomerIcon}
    label="Customers"
  />
  <NavigationBarItem 
    href="#payments" 
    icon={MoneyIcon} 
    label="Payments" 
  />
</NavigationBar>

API Reference

IndicatorBadge

PropTypeDescriptionDefault
children(props: { childProps }) => React.ReactNodeRender a function that receives childProps. Spread childProps onto the wrapped element to ensure accessibility and proper component functionality._
labelstringScreen reader text describing the indicator (e.g., "5 unread notifications")._
count?numberNumeric count displayed in the badge. The count displays only when count > 0, and when it exceeds 99 it shows "99+"._
variant?'neutralPrimary' | 'neutralSecondary' | 'criticalPrimary' | 'criticalSecondary' | 'positivePrimary' | 'positiveSecondary' | 'cautionPrimary' | 'cautionSecondary'Visual variant applied to indicator.'neutralPrimary'
placement?'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'Position of the indicator relative to the wrapped content.'topRight'
hasDot?booleanWhen true, renders a small dot indicator instead of a count badge. Takes priority over count.false
size?'standard' | 'small'Size of the indicator (for count badges).'small'
isVisible?booleanControls the visibility of the indicator. When false, neither the count nor dot will be displayed.true

Style API

Our design system components include style props that allow you to easily customize different parts of each component to match your design needs.

Please refer to the Style API documentation for more insights.

IndicatorBadge parts

Customize the appearance of IndicatorBadge using the classNames or styles props to target specific parts.

5 notifications with custom styling
<Box className="flex items-center gap-6">
  <IndicatorBadge
    count={5}
    label="5 notifications with custom styling"
    classNames={{
      statusBadge: 'bg-palette-pink-background-active text-accent',
      badgeWrapper: 'border-4 border-solid border-palette-blue-border'
    }}
  >
    {({ childProps }) => (
      <IconButton
        icon={NotificationsIcon}
        aria-label="notifications"
        {...childProps}
      />
    )}
  </IndicatorBadge>
   <IndicatorBadge
    hasDot
    classNames={{
      statusDot: 'bg-palette-pink-background-active text-accent border-2 border-solid border-palette-blue-border',
    }}
  >
    {({ childProps }) => (
      <IconButton
        icon={NotificationsIcon}
        aria-label="notifications"
        {...childProps}
      />
    )}
  </IndicatorBadge>
</Box>