diff --git a/ds-versions.json b/ds-versions.json
index 67481678f..d99999fff 100644
--- a/ds-versions.json
+++ b/ds-versions.json
@@ -46,6 +46,7 @@
"textbox": "1.1.0",
"toast": "2.1.0",
"toast-dialog": "2.1.0",
+ "toggle-button": "1.0.0",
"tooltip": "1.0.0",
"tourtip": "1.0.0",
"typography": "1.1.0"
diff --git a/src/components/ebay-toggle-button/README.md b/src/components/ebay-toggle-button/README.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/components/ebay-toggle-button/browser.json b/src/components/ebay-toggle-button/browser.json
new file mode 100644
index 000000000..b498bbf77
--- /dev/null
+++ b/src/components/ebay-toggle-button/browser.json
@@ -0,0 +1,9 @@
+{
+ "requireRemap": [
+ {
+ "from": "./style",
+ "to": "../../common/empty",
+ "if-flag": "ebayui-no-skin"
+ }
+ ]
+}
diff --git a/src/components/ebay-toggle-button/component.js b/src/components/ebay-toggle-button/component.js
new file mode 100644
index 000000000..18a0dad1e
--- /dev/null
+++ b/src/components/ebay-toggle-button/component.js
@@ -0,0 +1,13 @@
+export default class {
+ onInput(input) {
+ this.state = { pressed: input.pressed };
+ }
+
+ handleClick(ev) {
+ this.state.pressed = !this.state.pressed;
+ this.emit("toggle", {
+ originalEvent: ev,
+ pressed: this.state.pressed,
+ });
+ }
+}
diff --git a/src/components/ebay-toggle-button/examples/multiline-subtitle.marko b/src/components/ebay-toggle-button/examples/multiline-subtitle.marko
new file mode 100644
index 000000000..ce755c396
--- /dev/null
+++ b/src/components/ebay-toggle-button/examples/multiline-subtitle.marko
@@ -0,0 +1,6 @@
+
+ <@subtitle>
+ Subtitle 1
+ Subtitle 2
+ @subtitle>
+
diff --git a/src/components/ebay-toggle-button/examples/with-icon.marko b/src/components/ebay-toggle-button/examples/with-icon.marko
new file mode 100644
index 000000000..c2db721bb
--- /dev/null
+++ b/src/components/ebay-toggle-button/examples/with-icon.marko
@@ -0,0 +1,3 @@
+
+ <@icon>@icon>
+
diff --git a/src/components/ebay-toggle-button/examples/with-image.marko b/src/components/ebay-toggle-button/examples/with-image.marko
new file mode 100644
index 000000000..ba8129b5e
--- /dev/null
+++ b/src/components/ebay-toggle-button/examples/with-image.marko
@@ -0,0 +1,4 @@
+$ const { src, alt, fillPlacement, ...buttonInput } = input
+
+ <@img src=src alt=alt fillPlacement=fillPlacement />
+
diff --git a/src/components/ebay-toggle-button/index.marko b/src/components/ebay-toggle-button/index.marko
new file mode 100644
index 000000000..0d6dcea12
--- /dev/null
+++ b/src/components/ebay-toggle-button/index.marko
@@ -0,0 +1,59 @@
+import { processHtmlAttributes } from "../../common/html-attributes";
+
+static var ignoredAttributes = [
+ "class",
+ "title",
+ "subtitle",
+ "pressed"
+];
+
+
diff --git a/src/components/ebay-toggle-button/marko-tag.json b/src/components/ebay-toggle-button/marko-tag.json
new file mode 100644
index 000000000..5198ebdea
--- /dev/null
+++ b/src/components/ebay-toggle-button/marko-tag.json
@@ -0,0 +1,33 @@
+{
+ "attribute-groups": ["html-attributes"],
+ "@*": {
+ "targetProperty": null,
+ "type": "expression"
+ },
+ "@html-attributes": "expression",
+ "@pressed": "boolean",
+ "@title": "string",
+ "@subtitle ": {
+ "@*": {
+ "targetProperty": null,
+ "type": "expression"
+ },
+ "@html-attributes": "expression"
+ },
+ "@icon ": {
+ "@*": {
+ "targetProperty": null,
+ "type": "expression"
+ },
+ "@html-attributes": "expression"
+ },
+ "@img ": {
+ "@*": {
+ "targetProperty": null,
+ "type": "expression"
+ },
+ "@html-attributes": "expression",
+ "@src": "string",
+ "@size": "string"
+ }
+}
diff --git a/src/components/ebay-toggle-button/style.js b/src/components/ebay-toggle-button/style.js
new file mode 100644
index 000000000..f5aeb77fd
--- /dev/null
+++ b/src/components/ebay-toggle-button/style.js
@@ -0,0 +1 @@
+require("@ebay/skin/toggle-button");
diff --git a/src/components/ebay-toggle-button/toggle-button.stories.js b/src/components/ebay-toggle-button/toggle-button.stories.js
new file mode 100644
index 000000000..772f72ae8
--- /dev/null
+++ b/src/components/ebay-toggle-button/toggle-button.stories.js
@@ -0,0 +1,161 @@
+import { tagToString } from "../../../.storybook/storybook-code-source";
+import { addRenderBodies } from "../../../.storybook/utils";
+import readme from "./README.md";
+import component from "./index.marko";
+import WithIconTemplate from "./examples/with-icon.marko";
+import WithIconCode from "./examples/with-icon.marko?raw";
+import WithImageTemplate from "./examples/with-image.marko";
+import WithImageCode from "./examples/with-image.marko?raw";
+import MultilineSubtitleTemplate from "./examples/multiline-subtitle.marko";
+import MultilineSubtitleCode from "./examples/multiline-subtitle.marko?raw";
+
+const Template = (args) => ({
+ input: addRenderBodies(args),
+});
+
+export default {
+ title: "buttons/ebay-toggle-button",
+ component,
+ parameters: {
+ docs: {
+ description: {
+ component: readme,
+ },
+ },
+ },
+ argTypes: {
+ renderBody: {},
+ layoutType: {
+ type: "string",
+ control: { type: "select" },
+ options: [undefined, "list", "gallery"],
+ description:
+ 'Enforced layout type of the button. May be `undefined` (minimal default), `"list"`, or `"gallery"`. Gallery layout may only be used when there is also an icon or an image.',
+ },
+ pressed: {
+ type: "boolean",
+ control: { type: "boolean" },
+ description: "Pressed state of the button",
+ },
+ title: {
+ type: "string",
+ control: { type: "text" },
+ description: "Title attribute for the button",
+ },
+ subtitle: {
+ type: "string|@subtitle",
+ control: { type: "text" },
+ description: "Subtitle attribute for the button",
+ },
+ icon: {
+ name: "@icon",
+ description: "An `` to show as the button's icon",
+ table: {
+ category: "@attribute tags",
+ },
+ },
+ img: {
+ name: "@img",
+ description: "An `` to show as the button's image",
+ table: {
+ category: "@attribute tags",
+ },
+ },
+ subtitleTag: {
+ name: "@subtitle",
+ description:
+ "May be used instead of the `subtitle` attribute for more control. Should contain no more than two brief lines of text",
+ table: {
+ category: "@attribute tags",
+ },
+ },
+ src: {
+ table: {
+ category: "@img attributes",
+ },
+ control: { type: "text" },
+ description: "Link to the image source",
+ },
+ alt: {
+ table: {
+ category: "@img attributes",
+ },
+ control: { type: "text" },
+ description: "Alt text for the image",
+ },
+ fillPlacement: {
+ table: {
+ category: "@img attributes",
+ },
+ control: { type: "text" },
+ description:
+ "Placement of the image within the given bounds using the CSS `background-position` property. Options include [keywords, lengths, and edge distances](https://developer.mozilla.org/en-US/docs/Web/CSS/background-position). Using this property will switch the image fit from `contain` to `cover`",
+ },
+ onToggle: {
+ action: "on-toggle",
+ description: "Triggered when the button is toggled",
+ table: {
+ category: "Events",
+ defaultValue: {
+ summary: "{ originalEvent, pressed }",
+ },
+ },
+ },
+ },
+};
+
+export const Default = Template.bind({});
+Default.args = {
+ title: "Title",
+};
+
+Default.parameters = {
+ docs: {
+ source: {
+ code: tagToString("ebay-toggle-button", Default.args),
+ },
+ },
+};
+
+export const WithIcon = (args) => ({
+ input: args,
+ component: WithIconTemplate,
+});
+WithIcon.args = {};
+WithIcon.parameters = {
+ docs: {
+ source: {
+ code: WithIconCode,
+ },
+ },
+};
+
+export const WithImage = (args) => ({
+ input: args,
+ component: WithImageTemplate,
+});
+WithImage.args = {
+ layoutType: "gallery",
+ src: "https://cloudfront.slrlounge.com/wp-content/uploads/2012/07/01-SLRLounge-Holding-Standing-Wrong.jpg",
+ fillPlacement: "top",
+};
+WithImage.parameters = {
+ docs: {
+ source: {
+ code: WithImageCode,
+ },
+ },
+};
+
+export const MultilineSubtitle = (args) => ({
+ input: args,
+ component: MultilineSubtitleTemplate,
+});
+MultilineSubtitle.args = {};
+MultilineSubtitle.parameters = {
+ docs: {
+ source: {
+ code: MultilineSubtitleCode,
+ },
+ },
+};