Workday Canvas

Badge

Badge indicates quantitative data associated with a parent element.

v14.2.34
Install
yarn add @workday/canvas-kit-react

Anatomy

the basic elements and types of a Badge

  1. High Emphasis: For conveying notifications.
  2. Low Emphasis: For conveying non-pertinent quantitative data.
  3. High Emphasis Inverse: For use on backgrounds darker than 500 level.
  4. Low Emphasis Inverse: For use on backgrounds darker than 500 level.

Usage Guidance

When to Use

  • Use a Badge to indicate quantitative data associated with a parent element (e.g., icons, applets, Avatars, text, etc.).

When to Consider Something Else

Behaviors

Expansion

A Low Emphasis Badge should always expand rightwards from its absolute position when featuring a larger count and / or descriptive metadata. When expanding beyond 99, the count cuts off at 99+.

Badge showing a 99+ count in a Top App Bar

Screen Readers

When a Low Emphasis Badge is used in-line with text, the order in which it is read should be configurable, so it can be read how it makes the most logical sense. High Emphasis Badges do not have focus stops, since they do not support text.

High Emphasis

High Emphasis Badges are intended for usage as an indicator of notification.

Image of High Emphasis Badges on icons in the Main Navigation Bar

After navigating resolving the source of the notification, the Badge should no longer appear. However, High Emphasis Badges may not always disappear after resolving an item if it is representative of multiple pieces of data.

Low Emphasis

Low Emphasis Badges are intended for conveying less pertinent numerical information. By accepting text, these Badges also allow for additional descriptive text to be passed in to compliment the numerical data.

Low Emphasis Badges being used in section titles of an experience

Low Emphasis Badges could be used in the place of using parentheses to convey quantitative data.

After resolving the source of the indicator, the Badge should either augment down or not appear if it is representative of a standalone piece of data.

Examples

The following section provides examples for common use cases. Please be sure to also read the Accessibility section below.

Basic

Use the default CountBadge variant for most situations. The default high emphasis is ideal for drawing attention to important or primary information.

427
import {CountBadge} from '@workday/canvas-kit-react/badge';
import {createStyles} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';

const containerStyles = createStyles({
  display: 'flex',
  gap: system.space.x2,
  padding: system.space.x4,
});

export default () => {
  return (
    <div className={containerStyles}>
      <CountBadge count={427} />
    </div>
  );
};

Emphasis

Select the low emphasis option for less prominent or secondary information. This is useful when you want the badge to be visible but not distracting.

high emphasis is ideal for drawing attention to important or primary information.

Low Emphasis:427
High Emphasis:427
import {CountBadge} from '@workday/canvas-kit-react/badge';
import {createStyles} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
import {Text} from '@workday/canvas-kit-react/text';

const containerStyles = createStyles({
  display: 'flex',
  gap: system.space.x2,
  padding: system.space.x4,
  flexDirection: 'column',
});

const textStyles = createStyles({
  paddingInlineEnd: system.space.x2,
});

export default () => {
  return (
    <div className={containerStyles}>
      <div>
        <Text as="strong" className={textStyles}>
          Low Emphasis:
        </Text>
        <CountBadge count={427} emphasis="low" />
      </div>
      <div>
        <Text as="strong" className={textStyles}>
          High Emphasis:
        </Text>
        <CountBadge count={427} emphasis="high" />
      </div>
    </div>
  );
};

Inverse

Apply the inverse variant when displaying the badge on dark or accent backgrounds to maintain proper contrast and readability. This ensures the badge remains legible in visually dense or colored areas. The same rules apply for low and high emphasis. Only use this combination on backgrounds with a token of 600 or greater to ensure sufficient contrast and accessibility.

Low Emphasis:427
High Emphasis427
import {CountBadge} from '@workday/canvas-kit-react/badge';
import {createStyles, cssVar} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';
import {Text} from '@workday/canvas-kit-react/text';
const containerStyles = createStyles({
  display: 'flex',
  gap: system.space.x2,
  padding: system.space.x4,
  backgroundColor: system.color.static.blue.default,
  flexDirection: 'column',
});

const textStyles = createStyles({
  paddingInlineEnd: system.space.x2,
});

export default () => {
  return (
    <div className={containerStyles}>
      <div>
        <Text as="strong" variant="inverse" className={textStyles}>
          Low Emphasis:
        </Text>
        <CountBadge count={427} variant="inverse" emphasis="low" />
      </div>
      <div>
        <Text as="strong" variant="inverse" className={textStyles}>
          High Emphasis
        </Text>
        <CountBadge count={427} variant="inverse" emphasis="high" />
      </div>
    </div>
  );
};

Custom Limit

By default, CountBadge’s limit is set to 1000. Once the count reaches the limit, the badge will format the number: 1000 becomes 999+. The default limit is largely arbitrary and intended to prevent unexpected overflow. You should choose a limit based on your specific use case and consider the user’s experience. For example, someone looking for a new job finds there are 99+ new opportunities. Or perhaps someone returns from extended leave and is overwhelmed by 999+ unread messages on their first day back.

1
1
import * as React from 'react';
import {CountBadge} from '@workday/canvas-kit-react/badge';
import {TertiaryButton} from '@workday/canvas-kit-react/button';

import {createStyles} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';

const columnStyles = createStyles({
  boxSizing: 'border-box',
  display: 'flex',
  flexDirection: 'column',
  gap: system.space.x4,
});

const controls = createStyles({
  boxSizing: 'border-box',
  borderBottom: `solid 1px ${system.color.border.divider}`,
  display: 'flex',
  gap: system.space.x1,
  padding: system.space.x1,
});

const defaultBackground = createStyles({
  boxSizing: 'border-box',
  backgroundColor: system.color.bg.alt.soft,
  padding: system.space.x4,
});

const inverseBackground = createStyles({
  boxSizing: 'border-box',
  backgroundColor: system.color.bg.primary.default,
  padding: system.space.x4,
});

const initialCount = 1;

export default () => {
  const [count, setCount] = React.useState(initialCount);

  return (
    <div className={columnStyles}>
      <div className={controls}>
        <TertiaryButton size="small" onClick={() => setCount(count + 1)}>
          Increment
        </TertiaryButton>
        <TertiaryButton size="small" onClick={() => setCount(initialCount)}>
          Reset
        </TertiaryButton>
      </div>
      <div className={defaultBackground}>
        <CountBadge count={count} limit={10} />
      </div>
      <div className={inverseBackground}>
        <CountBadge count={count} limit={10} variant="inverse" />
      </div>
    </div>
  );
};

Accessibility

Notification Badge

Notifications are a major use case for CountBadge. When the CountBadge value is updated in real-time, screen readers must be supported with an AriaLiveRegion that will automatically describe the change in the number of notifications. If the web app only updates CountBadge as part of another screen update, then this use of AriaLiveRegion is unnecessary and not recommended.

  • Tooltip is set on the SecondaryButton automatically applying the aria-label to the button.
  • aria-describedby property is conditionally set on the SecondaryButton when greater than zero referencing a unique id for the CountBadge value .
  • AriaLiveRegion is used around the CountBadge, enabling screen readers to monitor changes in value.
  • aria-label string is conditionally set on AriaLiveRegion when greater than zero, describing “New notification”
4
import * as React from 'react';
import {CountBadge} from '@workday/canvas-kit-react/badge';
import {SecondaryButton, TertiaryButton} from '@workday/canvas-kit-react/button';
import {AriaLiveRegion, useUniqueId} from '@workday/canvas-kit-react/common';
import {createStyles, cssVar} from '@workday/canvas-kit-styling';
import {notificationsIcon} from '@workday/canvas-system-icons-web';
import {system} from '@workday/canvas-tokens-web';
import {Tooltip} from '@workday/canvas-kit-react/tooltip';
import {Flex} from '@workday/canvas-kit-react/layout';

function negate(value: string, fallback?: string) {
  return `calc(${cssVar(value, fallback)} * -1)`;
}

const container = createStyles({
  boxSizing: 'border-box',
  flexDirection: 'column',
  gap: system.space.x4,
});

const controls = createStyles({
  boxSizing: 'border-box',
  gap: system.space.x2,
  padding: system.space.x1,
});

const notificationContainerStyles = createStyles({
  boxSizing: 'border-box',
  position: 'relative',
});

const countBadgeStyles = createStyles({
  boxSizing: 'border-box',
  position: 'absolute',
  top: negate(system.space.x1),
  insetInlineEnd: negate(system.space.x1),
});

// Testing notes (Aug. 30, 2024):
// Windows 11
// JAWS 2024 + Chrome / Edge: "New notifications" once, then only the count change "2"
// JAWS 2024 + FF: "New notifications" once, then describes nothing
// NVDA + Chrome / Edge: Consistently describes "{X} New notifications"
// NVDA + FF: Consistently describes count value only "{X}"
// macOS v14.6.1
// VoiceOver + Chrome / Safari: Consistently describes "New notifications {X}"
export default () => {
  const [count, setCount] = React.useState(4);
  const badgeID = useUniqueId();

  return (
    <Flex cs={container}>
      <Flex cs={controls}>
        <TertiaryButton size="small" onClick={() => setCount(count + 1)}>
          Add Notification
        </TertiaryButton>
        <TertiaryButton size="small" onClick={() => setCount(0)}>
          Clear
        </TertiaryButton>
      </Flex>
      <Flex>
        <span className={notificationContainerStyles}>
          <Tooltip title="Notifications">
            <SecondaryButton
              size="medium"
              icon={notificationsIcon}
              aria-describedby={!!count ? badgeID : undefined}
            />
          </Tooltip>
          <AriaLiveRegion aria-label={!!count ? 'New notifications' : undefined}>
            {!!count && <CountBadge id={badgeID} count={count} limit={100} cs={countBadgeStyles} />}
          </AriaLiveRegion>
        </span>
      </Flex>
    </Flex>
  );
};

Custom Styles

Count Badge supports custom styling via the cs prop. For more information, check our “How To Customize Styles”.

Component API

CountBadge

CountBadge provides a quantity-based summary with dynamic values.

Props

Props extend from span. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
countnumber

Sets the count displayed in the badge

0
emphasis 'high' 'low'

Sets the emphasis of the badge

'high'
limitnumber

Sets the maximum count to display before formatting the number. E.g. Given a count of 100 and a limit of 100, the badge would display 99+.

1000
variant'inverse'

Sets the variant of the Count Badge

cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use {@link mergeStyles } instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';

// ...

// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.

return (
 <Element
   {...handleCsProp(elemProps, [
     myStyles,
     myModifiers({ size: 'medium' }),
     myVars({ backgroundColor: 'red' })
   ])}
 >
   {children}
 </Element>
)
childrenReact.ReactNode
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

span
refReact.Ref<R = span>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

Can't Find What You Need?

Check out our FAQ section which may help you find the information you're looking for. For further information, contact the #ask-canvas-design or #ask-canvas-kitchannels on Slack.

On this Page: