diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts
new file mode 100644
index 00000000000..fa3a915f227
--- /dev/null
+++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts
@@ -0,0 +1,176 @@
+import { Component } from "@angular/core";
+import { FormBuilder, Validators } from "@angular/forms";
+
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+
+import { DialogService } from "../../../dialog";
+import { I18nMockService } from "../../../utils/i18n-mock.service";
+import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module";
+
+@Component({
+ standalone: true,
+ selector: "bit-kitchen-sink-form",
+ imports: [KitchenSinkSharedModule],
+ providers: [
+ DialogService,
+ {
+ provide: I18nService,
+ useFactory: () => {
+ return new I18nMockService({
+ close: "Close",
+ checkboxRequired: "Option is required",
+ fieldsNeedAttention: "__$1__ field(s) above need your attention.",
+ inputEmail: "Input is not an email-address.",
+ inputMaxValue: (max) => `Input value must not exceed ${max}.`,
+ inputMinValue: (min) => `Input value must be at least ${min}.`,
+ inputRequired: "Input is required.",
+ multiSelectClearAll: "Clear all",
+ multiSelectLoading: "Retrieving options...",
+ multiSelectNotFound: "No items found",
+ multiSelectPlaceholder: "-- Type to Filter --",
+ required: "required",
+ selectPlaceholder: "-- Select --",
+ toggleVisibility: "Toggle visibility",
+ });
+ },
+ },
+ ],
+ template: `
+
+ `,
+})
+export class KitchenSinkForm {
+ constructor(
+ public dialogService: DialogService,
+ public formBuilder: FormBuilder,
+ ) {}
+
+ formObj = this.formBuilder.group({
+ favFeature: ["", [Validators.required]],
+ favColor: [undefined as string | undefined, [Validators.required]],
+ topWorstPasswords: [undefined as string | undefined],
+ loveSecurity: [false, [Validators.requiredTrue]],
+ current: ["yes"],
+ numPasswords: [null, [Validators.min(0), Validators.max(150)]],
+ password: ["", [Validators.required]],
+ });
+
+ submit = async () => {
+ await this.dialogService.openSimpleDialog({
+ title: "Confirm",
+ content: "Are you sure you want to submit?",
+ type: "primary",
+ acceptButtonText: "Yes",
+ cancelButtonText: "No",
+ acceptAction: async () => this.acceptDialog(),
+ });
+ };
+
+ acceptDialog() {
+ this.formObj.markAllAsTouched();
+ this.dialogService.closeAll();
+ }
+
+ colors = [
+ { value: "blue", name: "Blue" },
+ { value: "white", name: "White" },
+ { value: "gray", name: "Gray" },
+ ];
+
+ worstPasswords = [
+ { id: "1", listName: "1234", labelName: "1234" },
+ { id: "2", listName: "admin", labelName: "admin" },
+ { id: "3", listName: "password", labelName: "password" },
+ { id: "4", listName: "querty", labelName: "querty" },
+ { id: "5", listName: "letmein", labelName: "letmein" },
+ { id: "6", listName: "trustno1", labelName: "trustno1" },
+ { id: "7", listName: "1qaz2wsx", labelName: "1qaz2wsx" },
+ ];
+}
diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts
new file mode 100644
index 00000000000..ae4448eb76c
--- /dev/null
+++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts
@@ -0,0 +1,117 @@
+import { DialogRef } from "@angular/cdk/dialog";
+import { Component } from "@angular/core";
+
+import { DialogService } from "../../../dialog";
+import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module";
+
+import { KitchenSinkForm } from "./kitchen-sink-form.component";
+import { KitchenSinkTable } from "./kitchen-sink-table.component";
+import { KitchenSinkToggleList } from "./kitchen-sink-toggle-list.component";
+
+@Component({
+ standalone: true,
+ imports: [KitchenSinkSharedModule],
+ template: `
+
+ Dialog body text goes here.
+
+ OK
+ Cancel
+
+
+ `,
+})
+class KitchenSinkDialog {
+ constructor(public dialogRef: DialogRef) {}
+}
+
+@Component({
+ standalone: true,
+ selector: "bit-tab-main",
+ imports: [
+ KitchenSinkSharedModule,
+ KitchenSinkTable,
+ KitchenSinkToggleList,
+ KitchenSinkForm,
+ KitchenSinkDialog,
+ ],
+ template: `
+
+ Kitchen Sink test zone
+
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
+
+ The purpose of this story is to compose together all of our components. When snapshot tests
+ run, we'll be able to spot-check visual changes in a more app-like environment than just the
+ isolated stories. The stories for the Kitchen Sink exist to be tested by the Chromatic UI
+ tests.
+
+
+
+ NOTE: These stories will treat "Light & Dark" mode as "Light" mode. This is done to avoid a
+ bug with the way that we render the same component twice in the same iframe and how that
+ interacts with the router-outlet.
+
+
+
+
+
+
+
+
+ About
+
+
+ Open Dialog
+
+
+ Companies using Bitwarden
+
+
+
+ Survey
+
+
+
+
+
+
+
+ This tab is empty
+
+ Try searching for what you are looking for:
+
+ Note that the search bar is not functional
+
+
+
+
+
+ `,
+})
+export class KitchenSinkMainComponent {
+ constructor(public dialogService: DialogService) {}
+
+ openDefaultDialog() {
+ this.dialogService.open(KitchenSinkDialog);
+ }
+
+ navItems = [
+ { icon: "bwi-collection", name: "Password Managers", route: "/" },
+ { icon: "bwi-collection", name: "Favorites", route: "/" },
+ ];
+}
diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts
new file mode 100644
index 00000000000..3c6d6f11444
--- /dev/null
+++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts
@@ -0,0 +1,49 @@
+import { Component } from "@angular/core";
+
+import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module";
+
+@Component({
+ standalone: true,
+ selector: "bit-kitchen-sink-table",
+ imports: [KitchenSinkSharedModule],
+ template: `
+
+
+
+ Product
+ User
+ Options
+
+
+
+
+ Password Manager
+ Everyone
+
+
+
+ Anchor link
+ Another link
+
+ Button after divider
+
+
+
+
+ Secrets Manager
+ Developers
+
+
+
+ Anchor link
+ Another link
+
+ Button after divider
+
+
+
+
+
+ `,
+})
+export class KitchenSinkTable {}
diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts
new file mode 100644
index 00000000000..2804c9e8351
--- /dev/null
+++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts
@@ -0,0 +1,34 @@
+import { Component } from "@angular/core";
+
+import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module";
+
+@Component({
+ standalone: true,
+ selector: "bit-kitchen-sink-toggle-list",
+ imports: [KitchenSinkSharedModule],
+ template: `
+
+
+ All 3
+
+ Enterprise 2
+
+ Mid-sized 1
+
+
+
+
+ {{ company.name }}
+
+
+ `,
+})
+export class KitchenSinkToggleList {
+ selectedToggle: "all" | "large" | "small" = "all";
+
+ companyList = [
+ { name: "A large enterprise company", size: "large" },
+ { name: "Another enterprise company", size: "large" },
+ { name: "A smaller company", size: "small" },
+ ];
+}
diff --git a/libs/components/src/stories/kitchen-sink/index.ts b/libs/components/src/stories/kitchen-sink/index.ts
new file mode 100644
index 00000000000..1d13b231366
--- /dev/null
+++ b/libs/components/src/stories/kitchen-sink/index.ts
@@ -0,0 +1 @@
+export * from "./kitchen-sink.stories";
diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink-shared.module.ts b/libs/components/src/stories/kitchen-sink/kitchen-sink-shared.module.ts
new file mode 100644
index 00000000000..56e3a92e2a3
--- /dev/null
+++ b/libs/components/src/stories/kitchen-sink/kitchen-sink-shared.module.ts
@@ -0,0 +1,114 @@
+import { CommonModule } from "@angular/common";
+import { NgModule } from "@angular/core";
+import { FormsModule, ReactiveFormsModule } from "@angular/forms";
+import { RouterModule } from "@angular/router";
+
+import { AsyncActionsModule } from "../../async-actions";
+import { AvatarModule } from "../../avatar";
+import { BadgeModule } from "../../badge";
+import { BannerModule } from "../../banner";
+import { BreadcrumbsModule } from "../../breadcrumbs";
+import { ButtonModule } from "../../button";
+import { CalloutModule } from "../../callout";
+import { CheckboxModule } from "../../checkbox";
+import { ColorPasswordModule } from "../../color-password";
+import { DialogModule } from "../../dialog";
+import { FormControlModule } from "../../form-control";
+import { FormFieldModule } from "../../form-field";
+import { IconModule } from "../../icon";
+import { IconButtonModule } from "../../icon-button";
+import { InputModule } from "../../input";
+import { LayoutComponent } from "../../layout";
+import { LinkModule } from "../../link";
+import { MenuModule } from "../../menu";
+import { NavigationModule } from "../../navigation";
+import { NoItemsModule } from "../../no-items";
+import { PopoverModule } from "../../popover";
+import { ProgressModule } from "../../progress";
+import { RadioButtonModule } from "../../radio-button";
+import { SearchModule } from "../../search";
+import { SectionComponent } from "../../section";
+import { SelectModule } from "../../select";
+import { SharedModule } from "../../shared";
+import { TableModule } from "../../table";
+import { TabsModule } from "../../tabs";
+import { ToggleGroupModule } from "../../toggle-group";
+import { TypographyModule } from "../../typography";
+
+@NgModule({
+ imports: [
+ AsyncActionsModule,
+ AvatarModule,
+ BadgeModule,
+ BannerModule,
+ BreadcrumbsModule,
+ ButtonModule,
+ CalloutModule,
+ CheckboxModule,
+ ColorPasswordModule,
+ CommonModule,
+ DialogModule,
+ FormControlModule,
+ FormFieldModule,
+ FormsModule,
+ IconButtonModule,
+ IconModule,
+ InputModule,
+ LayoutComponent,
+ LinkModule,
+ MenuModule,
+ NavigationModule,
+ NoItemsModule,
+ PopoverModule,
+ ProgressModule,
+ RadioButtonModule,
+ ReactiveFormsModule,
+ RouterModule,
+ SearchModule,
+ SectionComponent,
+ SelectModule,
+ SharedModule,
+ TableModule,
+ TabsModule,
+ ToggleGroupModule,
+ TypographyModule,
+ ],
+ exports: [
+ AsyncActionsModule,
+ AvatarModule,
+ BadgeModule,
+ BannerModule,
+ BreadcrumbsModule,
+ ButtonModule,
+ CalloutModule,
+ CheckboxModule,
+ ColorPasswordModule,
+ CommonModule,
+ DialogModule,
+ FormControlModule,
+ FormFieldModule,
+ FormsModule,
+ IconButtonModule,
+ IconModule,
+ InputModule,
+ LayoutComponent,
+ LinkModule,
+ MenuModule,
+ NavigationModule,
+ NoItemsModule,
+ PopoverModule,
+ ProgressModule,
+ RadioButtonModule,
+ ReactiveFormsModule,
+ RouterModule,
+ SearchModule,
+ SectionComponent,
+ SelectModule,
+ SharedModule,
+ TableModule,
+ TabsModule,
+ ToggleGroupModule,
+ TypographyModule,
+ ],
+})
+export class KitchenSinkSharedModule {}
diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink.mdx b/libs/components/src/stories/kitchen-sink/kitchen-sink.mdx
new file mode 100644
index 00000000000..49493f749ee
--- /dev/null
+++ b/libs/components/src/stories/kitchen-sink/kitchen-sink.mdx
@@ -0,0 +1,15 @@
+import { Meta, Story } from "@storybook/addon-docs";
+
+import * as stories from "./kitchen-sink.stories";
+
+
+
+# Kitchen Sink
+
+The purpose of this story is to compose together all of our components. When snapshot tests run,
+we'll be able to spot-check visual changes in a more app-like environment than just the isolated
+stories. The stories for the Kitchen Sink exist to be tested by the Chromatic UI tests.
+
+NOTE: These stories will treat "Light & Dark" mode as "Light" mode. This is done to avoid a bug with
+the way that we render the same component twice in the same iframe and how that interacts with the
+`router-outlet`.
diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts
new file mode 100644
index 00000000000..70adb211915
--- /dev/null
+++ b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts
@@ -0,0 +1,172 @@
+import { importProvidersFrom } from "@angular/core";
+import { provideNoopAnimations } from "@angular/platform-browser/animations";
+import { RouterModule } from "@angular/router";
+import {
+ Meta,
+ StoryObj,
+ applicationConfig,
+ componentWrapperDecorator,
+ moduleMetadata,
+} from "@storybook/angular";
+import {
+ userEvent,
+ getAllByRole,
+ getByRole,
+ getByLabelText,
+ fireEvent,
+} from "@storybook/testing-library";
+
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+
+import { DialogService } from "../../dialog";
+import { LayoutComponent } from "../../layout";
+import { I18nMockService } from "../../utils/i18n-mock.service";
+
+import { KitchenSinkForm } from "./components/kitchen-sink-form.component";
+import { KitchenSinkMainComponent } from "./components/kitchen-sink-main.component";
+import { KitchenSinkTable } from "./components/kitchen-sink-table.component";
+import { KitchenSinkToggleList } from "./components/kitchen-sink-toggle-list.component";
+import { KitchenSinkSharedModule } from "./kitchen-sink-shared.module";
+
+export default {
+ title: "Documentation / Kitchen Sink",
+ component: LayoutComponent,
+ decorators: [
+ componentWrapperDecorator(
+ /**
+ * Applying a CSS transform makes a `position: fixed` element act like it is `position: relative`
+ * https://github.com/storybookjs/storybook/issues/8011#issue-490251969
+ */
+ (story) => {
+ return /* HTML */ `
+ ${story}
+
`;
+ },
+ ({ globals }) => {
+ /**
+ * avoid a bug with the way that we render the same component twice in the same iframe and how
+ * that interacts with the router-outlet
+ */
+ const themeOverride = globals["theme"] === "both" ? "light" : globals["theme"];
+ return { theme: themeOverride };
+ },
+ ),
+ moduleMetadata({
+ imports: [
+ KitchenSinkSharedModule,
+ KitchenSinkForm,
+ KitchenSinkMainComponent,
+ KitchenSinkTable,
+ KitchenSinkToggleList,
+ ],
+ providers: [
+ DialogService,
+ {
+ provide: I18nService,
+ useFactory: () => {
+ return new I18nMockService({
+ close: "Close",
+ search: "Search",
+ skipToContent: "Skip to content",
+ submenu: "submenu",
+ toggleCollapse: "toggle collapse",
+ });
+ },
+ },
+ ],
+ }),
+ applicationConfig({
+ providers: [
+ provideNoopAnimations(),
+ importProvidersFrom(
+ RouterModule.forRoot(
+ [
+ { path: "", redirectTo: "bitwarden", pathMatch: "full" },
+ { path: "bitwarden", component: KitchenSinkMainComponent },
+ ],
+ { useHash: true },
+ ),
+ ),
+ ],
+ }),
+ ],
+} as Meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: (args) => {
+ return {
+ props: args,
+ template: /* HTML */ `
+
+
+
+
+
+
+
+
+
+ `,
+ };
+ },
+};
+
+export const MenuOpen: Story = {
+ ...Default,
+ play: async (context) => {
+ const canvas = context.canvasElement;
+ const table = getByRole(canvas, "table");
+
+ const menuButton = getAllByRole(table, "button")[0];
+ await userEvent.click(menuButton);
+ },
+};
+
+export const DefaultDialogOpen: Story = {
+ ...Default,
+ play: (context) => {
+ const canvas = context.canvasElement;
+ const dialogButton = getByRole(canvas, "button", {
+ name: "Open Dialog",
+ });
+
+ // workaround for userEvent not firing in FF https://github.com/testing-library/user-event/issues/1075
+ fireEvent.click(dialogButton);
+ },
+};
+
+export const PopoverOpen: Story = {
+ ...Default,
+ play: async (context) => {
+ const canvas = context.canvasElement;
+ const passwordLabelIcon = getByLabelText(canvas, "A random password (required)", {
+ selector: "button",
+ });
+
+ await userEvent.click(passwordLabelIcon);
+ },
+};
+
+export const SimpleDialogOpen: Story = {
+ ...Default,
+ play: (context) => {
+ const canvas = context.canvasElement;
+ const submitButton = getByRole(canvas, "button", {
+ name: "Submit",
+ });
+
+ // workaround for userEvent not firing in FF https://github.com/testing-library/user-event/issues/1075
+ fireEvent.click(submitButton);
+ },
+};
+
+export const EmptyTab: Story = {
+ ...Default,
+ play: async (context) => {
+ const canvas = context.canvasElement;
+ const emptyTab = getByRole(canvas, "tab", { name: "Empty tab" });
+ await userEvent.click(emptyTab);
+ },
+};