diff --git a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html
index e04d302ea2c..919d01f2d51 100644
--- a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html
+++ b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html
@@ -27,8 +27,8 @@
{{ button.label | i18n }}
-
-
+
+
diff --git a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts
index 5a40b72daff..f873d25641b 100644
--- a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts
+++ b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts
@@ -5,7 +5,7 @@ import { RouterModule } from "@angular/router";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { BitSvg } from "@bitwarden/assets/svg";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { SvgModule, LinkModule } from "@bitwarden/components";
+import { SvgModule, LinkModule, BerryComponent } from "@bitwarden/components";
export type NavButton = {
label: string;
@@ -20,7 +20,7 @@ export type NavButton = {
@Component({
selector: "popup-tab-navigation",
templateUrl: "popup-tab-navigation.component.html",
- imports: [CommonModule, LinkModule, RouterModule, JslibModule, SvgModule],
+ imports: [CommonModule, LinkModule, RouterModule, JslibModule, SvgModule, BerryComponent],
host: {
class: "tw-block tw-size-full tw-flex tw-flex-col",
},
diff --git a/libs/components/src/berry/berry.component.html b/libs/components/src/berry/berry.component.html
new file mode 100644
index 00000000000..2a05f534843
--- /dev/null
+++ b/libs/components/src/berry/berry.component.html
@@ -0,0 +1,3 @@
+@if (type() === "status" || content()) {
+
{{ content() }}
+}
diff --git a/libs/components/src/berry/berry.component.ts b/libs/components/src/berry/berry.component.ts
new file mode 100644
index 00000000000..8e58b888f39
--- /dev/null
+++ b/libs/components/src/berry/berry.component.ts
@@ -0,0 +1,80 @@
+import { ChangeDetectionStrategy, Component, computed, input } from "@angular/core";
+
+export type BerryVariant =
+ | "primary"
+ | "subtle"
+ | "success"
+ | "warning"
+ | "danger"
+ | "accentPrimary"
+ | "contrast";
+
+/**
+ * The berry component is a compact visual indicator used to display short,
+ * supplemental status information about another element,
+ * like a navigation item, button, or icon button.
+ * They draw users’ attention to status changes or new notifications.
+ *
+ * > `NOTE:` The maximum displayed value is 999. If the value is over 999, a “+” character is appended to indicate more.
+ */
+@Component({
+ selector: "bit-berry",
+ templateUrl: "berry.component.html",
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class BerryComponent {
+ protected readonly variant = input
("primary");
+ protected readonly value = input();
+ protected readonly type = input<"status" | "count">("count");
+
+ protected readonly content = computed(() => {
+ const value = this.value();
+ const type = this.type();
+
+ if (type === "status" || !value || value < 0) {
+ return undefined;
+ }
+ return value > 999 ? "999+" : `${value}`;
+ });
+
+ protected readonly textColor = computed(() => {
+ return this.variant() === "contrast" ? "tw-text-fg-dark" : "tw-text-fg-white";
+ });
+
+ protected readonly padding = computed(() => {
+ return (this.value()?.toString().length ?? 0) > 2 ? "tw-px-1.5 tw-py-0.5" : "";
+ });
+
+ protected readonly containerClasses = computed(() => {
+ const baseClasses = [
+ "tw-inline-flex",
+ "tw-items-center",
+ "tw-justify-center",
+ "tw-align-middle",
+ "tw-text-xxs",
+ "tw-rounded-full",
+ ];
+
+ const typeClasses = {
+ status: ["tw-h-2", "tw-w-2"],
+ count: ["tw-h-4", "tw-min-w-4", this.padding()],
+ };
+
+ const variantClass = {
+ primary: "tw-bg-bg-brand",
+ subtle: "tw-bg-bg-contrast",
+ success: "tw-bg-bg-success",
+ warning: "tw-bg-bg-warning",
+ danger: "tw-bg-bg-danger",
+ accentPrimary: "tw-bg-fg-accent-primary-strong",
+ contrast: "tw-bg-bg-white",
+ };
+
+ return [
+ ...baseClasses,
+ ...typeClasses[this.type()],
+ variantClass[this.variant()],
+ this.textColor(),
+ ].join(" ");
+ });
+}
diff --git a/libs/components/src/berry/berry.mdx b/libs/components/src/berry/berry.mdx
new file mode 100644
index 00000000000..b79ed35cac8
--- /dev/null
+++ b/libs/components/src/berry/berry.mdx
@@ -0,0 +1,48 @@
+import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs/blocks";
+
+import * as stories from "./berry.stories";
+
+
+
+```ts
+import { BerryComponent } from "@bitwarden/components";
+```
+
+
+
+
+
+
+
+## Usage
+
+### Status
+
+- Use a status berry to indicate a new notification of a status change that is not related to a
+ specific count.
+
+
+
+### Count
+
+- Use a count berry with text to indicate item count information for multiple new notifications.
+
+
+
+### All Variants
+
+
+
+## Count Behavior
+
+- Counts of **1-99**: Display in a compact circular shape
+- Counts of **100-999**: Display in a pill shape with padding
+- Counts **over 999**: Display as "999+" to prevent overflow
+
+## Accessibility
+
+- Use berries as **supplemental visual indicators** alongside descriptive text
+- Ensure sufficient color contrast with surrounding elements
+- For screen readers, provide appropriate labels on parent elements that describe the berry's
+ meaning
+- Berries are decorative; important information should not rely solely on the berry color
diff --git a/libs/components/src/berry/berry.stories.ts b/libs/components/src/berry/berry.stories.ts
new file mode 100644
index 00000000000..0b71e7259d8
--- /dev/null
+++ b/libs/components/src/berry/berry.stories.ts
@@ -0,0 +1,167 @@
+import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
+
+import { BerryComponent } from "./berry.component";
+
+export default {
+ title: "Component Library/Berry",
+ component: BerryComponent,
+ decorators: [
+ moduleMetadata({
+ imports: [BerryComponent],
+ }),
+ ],
+ args: {
+ type: "count",
+ variant: "primary",
+ value: 5,
+ },
+ argTypes: {
+ type: {
+ control: "select",
+ options: ["status", "count"],
+ description: "The type of the berry, which determines its size and content",
+ table: {
+ category: "Inputs",
+ type: { summary: '"status" | "count"' },
+ defaultValue: { summary: '"count"' },
+ },
+ },
+ variant: {
+ control: "select",
+ options: ["primary", "subtle", "success", "warning", "danger", "accentPrimary", "contrast"],
+ description: "The visual style variant of the berry",
+ table: {
+ category: "Inputs",
+ type: { summary: "BerryVariant" },
+ defaultValue: { summary: "primary" },
+ },
+ },
+ value: {
+ control: "number",
+ description:
+ "Optional value to display for berries with type 'count'. Maximum displayed is 999, values above show '999+'. If undefined, a small small berry is shown. If 0 or negative, the berry is hidden.",
+ table: {
+ category: "Inputs",
+ type: { summary: "number | undefined" },
+ defaultValue: { summary: "undefined" },
+ },
+ },
+ },
+ parameters: {
+ design: {
+ type: "figma",
+ url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/branch/rKUVGKb7Kw3d6YGoQl6Ho7/Tailwind-Component-Library?node-id=38367-199458&p=f&m=dev",
+ },
+ },
+} as Meta;
+
+type Story = StoryObj;
+
+export const Primary: Story = {
+ render: (args) => ({
+ props: args,
+ template: ``,
+ }),
+};
+
+export const statusType: Story = {
+ render: (args) => ({
+ props: args,
+ template: `
+
+
+
+
+
+
+
+
+
+ `,
+ }),
+};
+
+export const countType: Story = {
+ render: (args) => ({
+ props: args,
+ template: `
+
+
+
+
+
+
+ `,
+ }),
+};
+
+export const AllVariants: Story = {
+ render: () => ({
+ template: `
+
+
+ Primary:
+
+
+
+
+
+
+
+
+ Subtle:
+
+
+
+
+
+
+
+
+ Success:
+
+
+
+
+
+
+
+
+ Warning:
+
+
+
+
+
+
+
+
+ Danger:
+
+
+
+
+
+
+
+
+ Accent primary:
+
+
+
+
+
+
+
+
+ Contrast:
+
+
+
+
+
+
+
+ `,
+ }),
+};
diff --git a/libs/components/src/berry/index.ts b/libs/components/src/berry/index.ts
new file mode 100644
index 00000000000..8f85908653e
--- /dev/null
+++ b/libs/components/src/berry/index.ts
@@ -0,0 +1 @@
+export * from "./berry.component";
diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts
index d92e0770e49..d0bb8576095 100644
--- a/libs/components/src/index.ts
+++ b/libs/components/src/index.ts
@@ -7,6 +7,7 @@ export * from "./avatar";
export * from "./badge-list";
export * from "./badge";
export * from "./banner";
+export * from "./berry";
export * from "./breadcrumbs";
export * from "./button";
export * from "./callout";
diff --git a/libs/components/tailwind.config.base.js b/libs/components/tailwind.config.base.js
index d8220c39ff8..5de00fac34f 100644
--- a/libs/components/tailwind.config.base.js
+++ b/libs/components/tailwind.config.base.js
@@ -317,6 +317,7 @@ module.exports = {
base: ["1rem", "150%"],
sm: ["0.875rem", "150%"],
xs: [".75rem", "150%"],
+ xxs: [".5rem", "150%"],
},
container: {
"@5xl": "1100px",