diff --git a/libs/components/src/tabs/index.ts b/libs/components/src/tabs/index.ts
index 9b45ff1d43b..e89b4294459 100644
--- a/libs/components/src/tabs/index.ts
+++ b/libs/components/src/tabs/index.ts
@@ -1,3 +1,5 @@
export * from "./tabs.module";
-export * from "./tab-group.component";
-export * from "./tab-item.component";
+export * from "./tab-group/tab-group.component";
+export * from "./tab-group/tab.component";
+export * from "./tab-nav-bar/tab-nav-bar.component";
+export * from "./tab-nav-bar/tab-link.component";
diff --git a/libs/components/src/tabs/shared/tab-header.component.ts b/libs/components/src/tabs/shared/tab-header.component.ts
new file mode 100644
index 00000000000..4712df0549a
--- /dev/null
+++ b/libs/components/src/tabs/shared/tab-header.component.ts
@@ -0,0 +1,14 @@
+import { Component } from "@angular/core";
+
+/**
+ * Component used for styling the tab header/background for both content and navigation tabs
+ */
+@Component({
+ selector: "bit-tab-header",
+ host: {
+ class:
+ "tw-h-16 tw-pl-4 tw-bg-background-alt tw-flex tw-items-end tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300",
+ },
+ template: ``,
+})
+export class TabHeaderComponent {}
diff --git a/libs/components/src/tabs/shared/tab-list-container.directive.ts b/libs/components/src/tabs/shared/tab-list-container.directive.ts
new file mode 100644
index 00000000000..1cf8a762d58
--- /dev/null
+++ b/libs/components/src/tabs/shared/tab-list-container.directive.ts
@@ -0,0 +1,12 @@
+import { Directive } from "@angular/core";
+
+/**
+ * Directive used for styling the container for bit tab labels
+ */
+@Directive({
+ selector: "[bitTabListContainer]",
+ host: {
+ class: "tw-inline-flex tw-flex-wrap tw-leading-5",
+ },
+})
+export class TabListContainerDirective {}
diff --git a/libs/components/src/tabs/shared/tab-list-item.directive.ts b/libs/components/src/tabs/shared/tab-list-item.directive.ts
new file mode 100644
index 00000000000..d96b7adbc84
--- /dev/null
+++ b/libs/components/src/tabs/shared/tab-list-item.directive.ts
@@ -0,0 +1,85 @@
+import { FocusableOption } from "@angular/cdk/a11y";
+import { Directive, ElementRef, HostBinding, Input } from "@angular/core";
+
+/**
+ * Directive used for styling tab header items for both nav links (anchor tags)
+ * and content tabs (button tags)
+ */
+@Directive({ selector: "[bitTabListItem]" })
+export class TabListItemDirective implements FocusableOption {
+ @Input() active: boolean;
+ @Input() disabled: boolean;
+
+ @HostBinding("attr.disabled")
+ get disabledAttr() {
+ return this.disabled || null; // native disabled attr must be null when false
+ }
+
+ constructor(private elementRef: ElementRef) {}
+
+ focus() {
+ this.elementRef.nativeElement.focus();
+ }
+
+ click() {
+ this.elementRef.nativeElement.click();
+ }
+
+ @HostBinding("class")
+ get classList(): string[] {
+ return this.baseClassList
+ .concat(this.active ? this.activeClassList : [])
+ .concat(this.disabled ? this.disabledClassList : []);
+ }
+
+ get baseClassList(): string[] {
+ return [
+ "tw-block",
+ "tw-relative",
+ "tw-py-2",
+ "tw-px-4",
+ "tw-font-semibold",
+ "tw-transition",
+ "tw-rounded-t",
+ "tw-border-0",
+ "tw-border-x",
+ "tw-border-t-4",
+ "tw-border-transparent",
+ "tw-border-solid",
+ "tw-bg-transparent",
+ "tw-text-main",
+ "hover:tw-underline",
+ "hover:tw-text-main",
+ "focus-visible:tw-z-10",
+ "focus-visible:tw-outline-none",
+ "focus-visible:tw-ring-2",
+ "focus-visible:tw-ring-primary-700",
+ ];
+ }
+
+ get disabledClassList(): string[] {
+ return [
+ "!tw-bg-secondary-100",
+ "!tw-text-muted",
+ "hover:!tw-text-muted",
+ "!tw-no-underline",
+ "tw-cursor-not-allowed",
+ ];
+ }
+
+ get activeClassList(): string[] {
+ return [
+ "tw--mb-px",
+ "tw-border-x-secondary-300",
+ "tw-border-t-primary-500",
+ "tw-border-b",
+ "tw-border-b-background",
+ "tw-bg-background",
+ "!tw-text-primary-500",
+ "hover:tw-border-t-primary-700",
+ "hover:!tw-text-primary-700",
+ "focus-visible:tw-border-t-primary-700",
+ "focus-visible:!tw-text-primary-700",
+ ];
+ }
+}
diff --git a/libs/components/src/tabs/tab-group.component.html b/libs/components/src/tabs/tab-group.component.html
deleted file mode 100644
index f4b0ced5c4c..00000000000
--- a/libs/components/src/tabs/tab-group.component.html
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
diff --git a/libs/components/src/tabs/tab-group.component.ts b/libs/components/src/tabs/tab-group.component.ts
deleted file mode 100644
index 856ab1f1e22..00000000000
--- a/libs/components/src/tabs/tab-group.component.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Component } from "@angular/core";
-
-@Component({
- selector: "bit-tab-group",
- templateUrl: "./tab-group.component.html",
-})
-export class TabGroupComponent {}
diff --git a/libs/components/src/tabs/tab-group/tab-body.component.html b/libs/components/src/tabs/tab-group/tab-body.component.html
new file mode 100644
index 00000000000..8134baf2a02
--- /dev/null
+++ b/libs/components/src/tabs/tab-group/tab-body.component.html
@@ -0,0 +1 @@
+
diff --git a/libs/components/src/tabs/tab-group/tab-body.component.ts b/libs/components/src/tabs/tab-group/tab-body.component.ts
new file mode 100644
index 00000000000..18baa49ed06
--- /dev/null
+++ b/libs/components/src/tabs/tab-group/tab-body.component.ts
@@ -0,0 +1,45 @@
+import { TemplatePortal } from "@angular/cdk/portal";
+import { Component, HostBinding, Input } from "@angular/core";
+
+@Component({
+ selector: "bit-tab-body",
+ templateUrl: "tab-body.component.html",
+})
+export class TabBodyComponent {
+ private _firstRender: boolean;
+
+ @Input() content: TemplatePortal;
+ @Input() preserveContent = false;
+
+ @HostBinding("attr.hidden") get hidden() {
+ return !this.active || null;
+ }
+
+ @Input()
+ get active() {
+ return this._active;
+ }
+ set active(value: boolean) {
+ this._active = value;
+ if (this._active) {
+ this._firstRender = true;
+ }
+ }
+ private _active: boolean;
+
+ /**
+ * The tab content to render.
+ * Inactive tabs that have never been rendered/active do not have their
+ * content rendered by default for performance. If `preserveContent` is `true`
+ * then the content persists after the first time content is rendered.
+ */
+ get tabContent() {
+ if (this.active) {
+ return this.content;
+ }
+ if (this.preserveContent && this._firstRender) {
+ return this.content;
+ }
+ return null;
+ }
+}
diff --git a/libs/components/src/tabs/tab-group/tab-group.component.html b/libs/components/src/tabs/tab-group/tab-group.component.html
new file mode 100644
index 00000000000..dbe71fb4b99
--- /dev/null
+++ b/libs/components/src/tabs/tab-group/tab-group.component.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
diff --git a/libs/components/src/tabs/tab-group/tab-group.component.ts b/libs/components/src/tabs/tab-group/tab-group.component.ts
new file mode 100644
index 00000000000..76859976dcc
--- /dev/null
+++ b/libs/components/src/tabs/tab-group/tab-group.component.ts
@@ -0,0 +1,187 @@
+import { FocusKeyManager } from "@angular/cdk/a11y";
+import { coerceNumberProperty } from "@angular/cdk/coercion";
+import {
+ AfterContentChecked,
+ AfterContentInit,
+ AfterViewInit,
+ Component,
+ ContentChildren,
+ EventEmitter,
+ Input,
+ OnDestroy,
+ Output,
+ QueryList,
+ ViewChildren,
+} from "@angular/core";
+import { Subject, takeUntil } from "rxjs";
+
+import { TabListItemDirective } from "../shared/tab-list-item.directive";
+
+import { TabComponent } from "./tab.component";
+
+/** Used to generate unique ID's for each tab component */
+let nextId = 0;
+
+@Component({
+ selector: "bit-tab-group",
+ templateUrl: "./tab-group.component.html",
+})
+export class TabGroupComponent
+ implements AfterContentChecked, AfterContentInit, AfterViewInit, OnDestroy
+{
+ private readonly _groupId: number;
+ private readonly destroy$ = new Subject();
+ private _indexToSelect: number | null = 0;
+
+ /**
+ * Aria label for the tab list menu
+ */
+ @Input() label = "";
+
+ /**
+ * Keep the content of off-screen tabs in the DOM.
+ * Useful for keeping