diff --git a/dev/breadcrumb.html b/dev/breadcrumb.html
new file mode 100644
index 00000000000..92aa76bb7d5
--- /dev/null
+++ b/dev/breadcrumb.html
@@ -0,0 +1,88 @@
+
+
+
+
+
+ vaadin-breadcrumb
+
+
+
+
+
+
+ Breadcrumb Examples
+
+
+
Basic Breadcrumb
+
+ Home
+ Products
+ Laptops
+ MacBook Pro
+
+
+
+
+
Short Breadcrumb
+
+ Home
+ Settings
+
+
+
+
+
Breadcrumb with Target
+
+ Home
+ Vaadin (opens in new tab)
+ Current Page
+
+
+
+
+
Disabled Items
+
+ Home
+ Products (disabled)
+ Current Page
+
+
+
+
+
Long Breadcrumb Trail
+
+ Home
+ Documentation
+ Components
+ Navigation
+ Breadcrumb
+ Examples
+
+
+
+
+
Router Ignore Example
+
+ Home
+ API (full page reload)
+ Current
+
+
+
+
\ No newline at end of file
diff --git a/packages/breadcrumb/package.json b/packages/breadcrumb/package.json
new file mode 100644
index 00000000000..4e601fd7ad9
--- /dev/null
+++ b/packages/breadcrumb/package.json
@@ -0,0 +1,54 @@
+{
+ "name": "@vaadin/breadcrumb",
+ "version": "25.0.0-alpha20",
+ "publishConfig": {
+ "access": "public"
+ },
+ "description": "vaadin-breadcrumb",
+ "license": "Apache-2.0",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/vaadin/web-components.git",
+ "directory": "packages/breadcrumb"
+ },
+ "author": "Vaadin Ltd",
+ "homepage": "https://vaadin.com/components",
+ "bugs": {
+ "url": "https://github.com/vaadin/web-components/issues"
+ },
+ "main": "vaadin-breadcrumb.js",
+ "module": "vaadin-breadcrumb.js",
+ "type": "module",
+ "files": [
+ "src",
+ "vaadin-*.d.ts",
+ "vaadin-*.js",
+ "web-types.json",
+ "web-types.lit.json"
+ ],
+ "keywords": [
+ "Vaadin",
+ "breadcrumb",
+ "navigation",
+ "web-components",
+ "web-component"
+ ],
+ "dependencies": {
+ "@open-wc/dedupe-mixin": "^1.3.0",
+ "@vaadin/a11y-base": "25.0.0-alpha20",
+ "@vaadin/component-base": "25.0.0-alpha20",
+ "@vaadin/vaadin-themable-mixin": "25.0.0-alpha20",
+ "lit": "^3.0.0"
+ },
+ "devDependencies": {
+ "@vaadin/chai-plugins": "25.0.0-alpha20",
+ "@vaadin/test-runner-commands": "25.0.0-alpha20",
+ "@vaadin/testing-helpers": "^2.0.0",
+ "@vaadin/vaadin-lumo-styles": "25.0.0-alpha20",
+ "sinon": "^21.0.0"
+ },
+ "web-types": [
+ "web-types.json",
+ "web-types.lit.json"
+ ]
+}
\ No newline at end of file
diff --git a/packages/breadcrumb/src/styles/vaadin-breadcrumb-item-styles.js b/packages/breadcrumb/src/styles/vaadin-breadcrumb-item-styles.js
new file mode 100644
index 00000000000..4fdffec621f
--- /dev/null
+++ b/packages/breadcrumb/src/styles/vaadin-breadcrumb-item-styles.js
@@ -0,0 +1,69 @@
+/**
+ * @license
+ * Copyright (c) 2017 - 2025 Vaadin Ltd.
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
+ */
+import '@vaadin/component-base/src/styles/style-props.js';
+import '@vaadin/vaadin-lumo-styles/icons.js';
+import { css } from 'lit';
+
+export const breadcrumbItemStyles = css`
+ :host {
+ display: inline-flex;
+ align-items: center;
+ white-space: nowrap;
+ }
+
+ :host([hidden]) {
+ display: none !important;
+ }
+
+ [part='link'] {
+ display: inline-flex;
+ align-items: center;
+ text-decoration: var(--vaadin-breadcrumb-link-text-decoration, none);
+ color: var(--vaadin-breadcrumb-link-color, var(--vaadin-secondary-text-color));
+ transition: color 0.2s;
+ outline: none;
+ cursor: var(--vaadin-clickable-cursor);
+ }
+
+ a[part='link']:hover {
+ text-decoration: var(--vaadin-breadcrumb-link-hover-text-decoration, underline);
+ color: var(--vaadin-breadcrumb-link-hover-color, var(--vaadin-primary-text-color));
+ }
+
+ a[part='link']:focus-visible {
+ border-radius: var(--vaadin-radius-s);
+ box-shadow: 0 0 0 2px var(--vaadin-focus-ring-color);
+ }
+
+ span[part='link'] {
+ color: var(--vaadin-breadcrumb-current-color, var(--vaadin-primary-text-color));
+ cursor: default;
+ }
+
+ :host([disabled]) [part='link'] {
+ color: var(--vaadin-disabled-text-color);
+ cursor: default;
+ pointer-events: none;
+ }
+
+ :host([last]) [part='link'] {
+ color: var(--vaadin-breadcrumb-current-color, var(--vaadin-primary-text-color));
+ font-weight: var(--vaadin-breadcrumb-current-font-weight, 500);
+ }
+
+ [part='separator'] {
+ display: inline-flex;
+ align-items: center;
+ margin: 0 var(--vaadin-breadcrumb-separator-spacing, var(--vaadin-space-xs));
+ color: var(--vaadin-breadcrumb-separator-color, var(--vaadin-tertiary-text-color));
+ }
+
+ [part='separator']::after {
+ content: var(--vaadin-breadcrumb-separator, var(--lumo-icons-angle-right));
+ font-family: var(--vaadin-breadcrumb-separator-font-family, 'lumo-icons');
+ font-size: var(--vaadin-breadcrumb-separator-size, var(--vaadin-icon-size-s));
+ }
+`;
diff --git a/packages/breadcrumb/src/styles/vaadin-breadcrumb-styles.js b/packages/breadcrumb/src/styles/vaadin-breadcrumb-styles.js
new file mode 100644
index 00000000000..f6147be5a91
--- /dev/null
+++ b/packages/breadcrumb/src/styles/vaadin-breadcrumb-styles.js
@@ -0,0 +1,29 @@
+/**
+ * @license
+ * Copyright (c) 2017 - 2025 Vaadin Ltd.
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
+ */
+import '@vaadin/component-base/src/styles/style-props.js';
+import '@vaadin/vaadin-lumo-styles/icons.js';
+import { css } from 'lit';
+
+export const breadcrumbStyles = css`
+ :host {
+ display: block;
+ font-size: var(--vaadin-breadcrumb-font-size, var(--vaadin-font-size-s));
+ line-height: var(--vaadin-breadcrumb-line-height, var(--vaadin-line-height-xs));
+ }
+
+ :host([hidden]) {
+ display: none !important;
+ }
+
+ [part='list'] {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ }
+`;
diff --git a/packages/breadcrumb/src/vaadin-breadcrumb-item.d.ts b/packages/breadcrumb/src/vaadin-breadcrumb-item.d.ts
new file mode 100644
index 00000000000..cd908d7368e
--- /dev/null
+++ b/packages/breadcrumb/src/vaadin-breadcrumb-item.d.ts
@@ -0,0 +1,67 @@
+/**
+ * @license
+ * Copyright (c) 2017 - 2025 Vaadin Ltd.
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
+ */
+import { DisabledMixin } from '@vaadin/a11y-base/src/disabled-mixin.js';
+import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
+import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
+import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
+
+/**
+ * `` is a Web Component for displaying a single item in a breadcrumb trail.
+ *
+ * ⚠️ **This component is experimental** and the API may change. In order to use it, enable the feature flag by setting `window.Vaadin.featureFlags.breadcrumbComponent = true`.
+ *
+ * ```html
+ * Products
+ * ```
+ *
+ * ### Styling
+ *
+ * The following shadow DOM parts are available for styling:
+ *
+ * Part name | Description
+ * -------------|----------------
+ * `link` | The link element
+ * `separator` | The separator element
+ *
+ * The following state attributes are available for styling:
+ *
+ * Attribute | Description
+ * ------------|-------------
+ * `disabled` | Set when the element is disabled
+ * `last` | Set when this is the last item in the breadcrumb
+ * `current` | Set when the item's href matches the current page
+ *
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
+ */
+declare class BreadcrumbItem extends DisabledMixin(DirMixin(ElementMixin(ThemableMixin(HTMLElement)))) {
+ /**
+ * The URL to navigate to
+ */
+ href: string | null | undefined;
+
+ /**
+ * The target of the link
+ */
+ target: string | null | undefined;
+
+ /**
+ * Whether to exclude the item from client-side routing
+ */
+ routerIgnore: boolean;
+
+ /**
+ * Whether the item's href matches the current page
+ */
+ readonly current: boolean;
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'vaadin-breadcrumb-item': BreadcrumbItem;
+ }
+}
+
+export { BreadcrumbItem };
diff --git a/packages/breadcrumb/src/vaadin-breadcrumb-item.js b/packages/breadcrumb/src/vaadin-breadcrumb-item.js
new file mode 100644
index 00000000000..cafc46efa19
--- /dev/null
+++ b/packages/breadcrumb/src/vaadin-breadcrumb-item.js
@@ -0,0 +1,206 @@
+/**
+ * @license
+ * Copyright (c) 2017 - 2025 Vaadin Ltd.
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
+ */
+import { html, LitElement } from 'lit';
+import { ifDefined } from 'lit/directives/if-defined.js';
+import { DisabledMixin } from '@vaadin/a11y-base/src/disabled-mixin.js';
+import { defineCustomElement } from '@vaadin/component-base/src/define.js';
+import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
+import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
+import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
+import { matchPaths } from '@vaadin/component-base/src/url-utils.js';
+import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
+import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
+import { breadcrumbItemStyles } from './styles/vaadin-breadcrumb-item-styles.js';
+
+/**
+ * `` is a Web Component for displaying a single item in a breadcrumb trail.
+ *
+ * ⚠️ **This component is experimental** and the API may change. In order to use it, enable the feature flag by setting `window.Vaadin.featureFlags.breadcrumbComponent = true`.
+ *
+ * ```html
+ * Products
+ * ```
+ *
+ * ### Styling
+ *
+ * The following shadow DOM parts are available for styling:
+ *
+ * Part name | Description
+ * -------------|----------------
+ * `link` | The link element
+ * `separator` | The separator element
+ *
+ * The following state attributes are available for styling:
+ *
+ * Attribute | Description
+ * ------------|-------------
+ * `disabled` | Set when the element is disabled
+ * `last` | Set when this is the last item in the breadcrumb
+ * `current` | Set when the item's href matches the current page
+ *
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
+ *
+ * @customElement
+ * @extends HTMLElement
+ * @mixes DisabledMixin
+ * @mixes DirMixin
+ * @mixes ElementMixin
+ * @mixes ThemableMixin
+ */
+class BreadcrumbItem extends DisabledMixin(
+ DirMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(LitElement))))),
+) {
+ static get is() {
+ return 'vaadin-breadcrumb-item';
+ }
+
+ static get experimental() {
+ return 'breadcrumbComponent';
+ }
+
+ static get styles() {
+ return breadcrumbItemStyles;
+ }
+
+ static get properties() {
+ return {
+ /**
+ * The URL to navigate to
+ */
+ href: {
+ type: String,
+ },
+
+ /**
+ * The target of the link
+ */
+ target: {
+ type: String,
+ },
+
+ /**
+ * Whether to exclude the item from client-side routing
+ * @type {boolean}
+ * @attr {boolean} router-ignore
+ */
+ routerIgnore: {
+ type: Boolean,
+ value: false,
+ },
+
+ /**
+ * Whether this is the last item in the breadcrumb
+ * @type {boolean}
+ * @private
+ */
+ _last: {
+ type: Boolean,
+ value: false,
+ reflectToAttribute: true,
+ attribute: 'last',
+ },
+
+ /**
+ * Whether the item's href matches the current page
+ * @type {boolean}
+ */
+ current: {
+ type: Boolean,
+ value: false,
+ readOnly: true,
+ reflectToAttribute: true,
+ },
+ };
+ }
+
+ constructor() {
+ super();
+ this.__boundUpdateCurrent = this.__updateCurrent.bind(this);
+ }
+
+ /** @protected */
+ render() {
+ return html`
+ ${this.href && !this._last
+ ? html`
+
+
+
+ `
+ : html`
+
+
+
+ `}
+ ${!this._last ? html`` : ''}
+ `;
+ }
+
+ /** @protected */
+ firstUpdated() {
+ super.firstUpdated();
+
+ if (!this.hasAttribute('role')) {
+ this.setAttribute('role', 'listitem');
+ }
+ }
+
+ /** @protected */
+ updated(props) {
+ super.updated(props);
+
+ if (props.has('href')) {
+ this.__updateCurrent();
+ }
+ }
+
+ /** @protected */
+ connectedCallback() {
+ super.connectedCallback();
+ this.__updateCurrent();
+
+ window.addEventListener('popstate', this.__boundUpdateCurrent);
+ window.addEventListener('vaadin-navigated', this.__boundUpdateCurrent);
+ }
+
+ /** @protected */
+ disconnectedCallback() {
+ super.disconnectedCallback();
+
+ window.removeEventListener('popstate', this.__boundUpdateCurrent);
+ window.removeEventListener('vaadin-navigated', this.__boundUpdateCurrent);
+ }
+
+ /**
+ * @param {boolean} last
+ * @private
+ */
+ _setLast(last) {
+ this._last = last;
+ }
+
+ /** @private */
+ __updateCurrent() {
+ if (!this.href) {
+ this._setCurrent(false);
+ return;
+ }
+
+ const browserPath = `${location.pathname}${location.search}`;
+ this._setCurrent(matchPaths(browserPath, this.href));
+ }
+}
+
+defineCustomElement(BreadcrumbItem);
+
+export { BreadcrumbItem };
diff --git a/packages/breadcrumb/src/vaadin-breadcrumb.d.ts b/packages/breadcrumb/src/vaadin-breadcrumb.d.ts
new file mode 100644
index 00000000000..61a1f64cbe3
--- /dev/null
+++ b/packages/breadcrumb/src/vaadin-breadcrumb.d.ts
@@ -0,0 +1,45 @@
+/**
+ * @license
+ * Copyright (c) 2017 - 2025 Vaadin Ltd.
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
+ */
+import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
+import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
+
+/**
+ * `` is a Web Component for displaying hierarchical navigation.
+ *
+ * ⚠️ **This component is experimental** and the API may change. In order to use it, enable the feature flag by setting `window.Vaadin.featureFlags.breadcrumbComponent = true`.
+ *
+ * ```html
+ *
+ * Home
+ * Products
+ * Details
+ *
+ * ```
+ *
+ * ### Styling
+ *
+ * The following shadow DOM parts are available for styling:
+ *
+ * Part name | Description
+ * -----------|----------------
+ * `list` | The ordered list element
+ *
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
+ */
+declare class Breadcrumb extends ElementMixin(ThemableMixin(HTMLElement)) {
+ /**
+ * The aria-label attribute for the breadcrumb navigation
+ */
+ ariaLabel: string;
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'vaadin-breadcrumb': Breadcrumb;
+ }
+}
+
+export { Breadcrumb };
diff --git a/packages/breadcrumb/src/vaadin-breadcrumb.js b/packages/breadcrumb/src/vaadin-breadcrumb.js
new file mode 100644
index 00000000000..b7510bd8bad
--- /dev/null
+++ b/packages/breadcrumb/src/vaadin-breadcrumb.js
@@ -0,0 +1,119 @@
+/**
+ * @license
+ * Copyright (c) 2017 - 2025 Vaadin Ltd.
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
+ */
+import './vaadin-breadcrumb-item.js';
+import { html, LitElement } from 'lit';
+import { defineCustomElement } from '@vaadin/component-base/src/define.js';
+import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
+import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
+import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
+import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
+import { breadcrumbStyles } from './styles/vaadin-breadcrumb-styles.js';
+
+/**
+ * `` is a Web Component for displaying hierarchical navigation.
+ *
+ * ⚠️ **This component is experimental** and the API may change. In order to use it, enable the feature flag by setting `window.Vaadin.featureFlags.breadcrumbComponent = true`.
+ *
+ * ```html
+ *
+ * Home
+ * Products
+ * Details
+ *
+ * ```
+ *
+ * ### Styling
+ *
+ * The following shadow DOM parts are available for styling:
+ *
+ * Part name | Description
+ * -----------|----------------
+ * `list` | The ordered list element
+ *
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
+ *
+ * @customElement
+ * @extends HTMLElement
+ * @mixes ElementMixin
+ * @mixes ThemableMixin
+ */
+class Breadcrumb extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(LitElement)))) {
+ static get is() {
+ return 'vaadin-breadcrumb';
+ }
+
+ static get experimental() {
+ return true;
+ }
+
+ static get styles() {
+ return breadcrumbStyles;
+ }
+
+ static get properties() {
+ return {
+ /**
+ * The aria-label attribute for the breadcrumb navigation
+ */
+ ariaLabel: {
+ type: String,
+ value: 'Breadcrumb',
+ reflectToAttribute: true,
+ sync: true,
+ },
+
+ /**
+ * The list of breadcrumb items
+ * @type {!Array}
+ * @private
+ */
+ _items: {
+ type: Array,
+ },
+ };
+ }
+
+ constructor() {
+ super();
+ this._items = [];
+ }
+
+ /** @protected */
+ render() {
+ return html`
+
+
+
+ `;
+ }
+
+ /** @protected */
+ firstUpdated() {
+ super.firstUpdated();
+
+ if (!this.hasAttribute('role')) {
+ this.setAttribute('role', 'navigation');
+ }
+ }
+
+ /** @private */
+ _onSlotChange() {
+ const slot = this.shadowRoot.querySelector('slot');
+ const items = slot.assignedElements().filter((el) => el.localName === 'vaadin-breadcrumb-item');
+
+ this._items = items;
+
+ // Update aria-current for the last item
+ items.forEach((item, index) => {
+ const isLast = index === items.length - 1;
+ item._setLast(isLast);
+ });
+ }
+}
+
+defineCustomElement(Breadcrumb);
+
+export { Breadcrumb };
diff --git a/packages/breadcrumb/test/breadcrumb.test.js b/packages/breadcrumb/test/breadcrumb.test.js
new file mode 100644
index 00000000000..31adfc5016d
--- /dev/null
+++ b/packages/breadcrumb/test/breadcrumb.test.js
@@ -0,0 +1,165 @@
+import { expect } from '@vaadin/chai-plugins';
+import { fixtureSync, nextFrame } from '@vaadin/testing-helpers';
+
+// Enable experimental breadcrumb component
+window.Vaadin ??= {};
+window.Vaadin.featureFlags ??= {};
+window.Vaadin.featureFlags.breadcrumbComponent = true;
+
+import '../vaadin-breadcrumb.js';
+import '../vaadin-breadcrumb-item.js';
+
+describe('vaadin-breadcrumb', () => {
+ let breadcrumb;
+
+ beforeEach(async () => {
+ breadcrumb = fixtureSync(`
+
+ Home
+ Products
+ Current
+
+ `);
+ await nextFrame();
+ });
+
+ describe('basic functionality', () => {
+ it('should have proper tag name', () => {
+ expect(breadcrumb.localName).to.equal('vaadin-breadcrumb');
+ });
+
+ it('should have navigation role', () => {
+ expect(breadcrumb.getAttribute('role')).to.equal('navigation');
+ });
+
+ it('should have aria-label', () => {
+ expect(breadcrumb.getAttribute('aria-label')).to.equal('Breadcrumb');
+ });
+
+ it('should contain breadcrumb items', () => {
+ const items = breadcrumb.querySelectorAll('vaadin-breadcrumb-item');
+ expect(items).to.have.length(3);
+ });
+ });
+
+ describe('item management', () => {
+ it('should mark last item as last', () => {
+ const items = breadcrumb.querySelectorAll('vaadin-breadcrumb-item');
+ expect(items[0].hasAttribute('last')).to.be.false;
+ expect(items[1].hasAttribute('last')).to.be.false;
+ expect(items[2].hasAttribute('last')).to.be.true;
+ });
+
+ it('should update last attribute when items change', async () => {
+ const newItem = document.createElement('vaadin-breadcrumb-item');
+ newItem.textContent = 'New Item';
+ breadcrumb.appendChild(newItem);
+ await nextFrame();
+
+ const items = breadcrumb.querySelectorAll('vaadin-breadcrumb-item');
+ expect(items[2].hasAttribute('last')).to.be.false;
+ expect(items[3].hasAttribute('last')).to.be.true;
+ });
+ });
+});
+
+describe('vaadin-breadcrumb-item', () => {
+ let item;
+
+ beforeEach(async () => {
+ item = fixtureSync('Test Item');
+ await nextFrame();
+ });
+
+ describe('basic functionality', () => {
+ it('should have proper tag name', () => {
+ expect(item.localName).to.equal('vaadin-breadcrumb-item');
+ });
+
+ it('should have listitem role', () => {
+ expect(item.getAttribute('role')).to.equal('listitem');
+ });
+
+ it('should render link when href is set', () => {
+ const link = item.shadowRoot.querySelector('a[part="link"]');
+ expect(link).to.exist;
+ expect(link.getAttribute('href')).to.equal('/test');
+ });
+
+ it('should render span when href is not set', async () => {
+ const noHrefItem = fixtureSync('No Link');
+ await nextFrame();
+
+ const span = noHrefItem.shadowRoot.querySelector('span[part="link"]');
+ const link = noHrefItem.shadowRoot.querySelector('a[part="link"]');
+ expect(span).to.exist;
+ expect(link).to.not.exist;
+ });
+ });
+
+ describe('separator', () => {
+ it('should render separator by default', () => {
+ const separator = item.shadowRoot.querySelector('[part="separator"]');
+ expect(separator).to.exist;
+ });
+
+ it('should not render separator for last item', async () => {
+ item._setLast(true);
+ await nextFrame();
+
+ const separator = item.shadowRoot.querySelector('[part="separator"]');
+ expect(separator).to.not.exist;
+ });
+ });
+
+ describe('disabled state', () => {
+ it('should handle disabled state', async () => {
+ item.disabled = true;
+ await nextFrame();
+
+ const link = item.shadowRoot.querySelector('[part="link"]');
+ expect(link.getAttribute('href')).to.be.null;
+ expect(link.getAttribute('tabindex')).to.equal('-1');
+ });
+ });
+
+ describe('target attribute', () => {
+ it('should set target on link', async () => {
+ item.target = '_blank';
+ await nextFrame();
+
+ const link = item.shadowRoot.querySelector('[part="link"]');
+ expect(link.getAttribute('target')).to.equal('_blank');
+ });
+ });
+
+ describe('router-ignore attribute', () => {
+ it('should set router-ignore on link', async () => {
+ item.routerIgnore = true;
+ await nextFrame();
+
+ const link = item.shadowRoot.querySelector('[part="link"]');
+ expect(link.hasAttribute('router-ignore')).to.be.true;
+ });
+ });
+
+ describe('last item behavior', () => {
+ it('should render span instead of link when last', async () => {
+ item._setLast(true);
+ await nextFrame();
+
+ const span = item.shadowRoot.querySelector('span[part="link"]');
+ const link = item.shadowRoot.querySelector('a[part="link"]');
+ expect(span).to.exist;
+ expect(link).to.not.exist;
+ });
+
+ it('should set aria-current="page" for last item', async () => {
+ item._setLast(true);
+ await nextFrame();
+
+ const span = item.shadowRoot.querySelector('[part="link"]');
+ expect(span.getAttribute('aria-current')).to.equal('page');
+ });
+ });
+});
diff --git a/packages/breadcrumb/test/dom/__snapshots__/breadcrumb.test.snap.js b/packages/breadcrumb/test/dom/__snapshots__/breadcrumb.test.snap.js
new file mode 100644
index 00000000000..4bf48ce9c70
--- /dev/null
+++ b/packages/breadcrumb/test/dom/__snapshots__/breadcrumb.test.snap.js
@@ -0,0 +1,273 @@
+/* @web/test-runner snapshot v1 */
+export const snapshots = {};
+
+snapshots["vaadin-breadcrumb breadcrumb host default"] =
+`
+
+ Home
+
+
+ Products
+
+
+ Current Page
+
+
+`;
+/* end snapshot vaadin-breadcrumb breadcrumb host default */
+
+snapshots["vaadin-breadcrumb breadcrumb host focused"] =
+`
+
+ Home
+
+
+ Products
+
+
+ Current Page
+
+
+`;
+/* end snapshot vaadin-breadcrumb breadcrumb host focused */
+
+snapshots["vaadin-breadcrumb breadcrumb host focus-ring"] =
+`
+
+ Home
+
+
+ Products
+
+
+ Current Page
+
+
+`;
+/* end snapshot vaadin-breadcrumb breadcrumb host focus-ring */
+
+snapshots["vaadin-breadcrumb breadcrumb shadow default"] =
+`
+
+
+
+`;
+/* end snapshot vaadin-breadcrumb breadcrumb shadow default */
+
+snapshots["vaadin-breadcrumb-item item host default with href"] =
+`
+ Products
+
+`;
+/* end snapshot vaadin-breadcrumb-item item host default with href */
+
+snapshots["vaadin-breadcrumb-item item host default without href"] =
+`
+ Current Page
+
+`;
+/* end snapshot vaadin-breadcrumb-item item host default without href */
+
+snapshots["vaadin-breadcrumb-item item host disabled"] =
+`
+ Products
+
+`;
+/* end snapshot vaadin-breadcrumb-item item host disabled */
+
+snapshots["vaadin-breadcrumb-item item host with target"] =
+`
+ Vaadin
+
+`;
+/* end snapshot vaadin-breadcrumb-item item host with target */
+
+snapshots["vaadin-breadcrumb-item item host router-ignore"] =
+`
+ API
+
+`;
+/* end snapshot vaadin-breadcrumb-item item host router-ignore */
+
+snapshots["vaadin-breadcrumb-item item host focused"] =
+`
+ Products
+
+`;
+/* end snapshot vaadin-breadcrumb-item item host focused */
+
+snapshots["vaadin-breadcrumb-item item host focus-ring"] =
+`
+ Products
+
+`;
+/* end snapshot vaadin-breadcrumb-item item host focus-ring */
+
+snapshots["vaadin-breadcrumb-item item host last item with href"] =
+`
+ Products
+
+`;
+/* end snapshot vaadin-breadcrumb-item item host last item with href */
+
+snapshots["vaadin-breadcrumb-item item host last item without href"] =
+`
+ Current Page
+
+`;
+/* end snapshot vaadin-breadcrumb-item item host last item without href */
+
+snapshots["vaadin-breadcrumb-item item host current"] =
+`
+ Home
+
+`;
+/* end snapshot vaadin-breadcrumb-item item host current */
+
+snapshots["vaadin-breadcrumb-item item shadow default with href"] =
+`
+
+
+
+
+
+`;
+/* end snapshot vaadin-breadcrumb-item item shadow default with href */
+
+snapshots["vaadin-breadcrumb-item item shadow default without href"] =
+`
+
+
+
+
+
+`;
+/* end snapshot vaadin-breadcrumb-item item shadow default without href */
+
+snapshots["vaadin-breadcrumb-item item shadow disabled"] =
+`
+
+
+
+
+
+`;
+/* end snapshot vaadin-breadcrumb-item item shadow disabled */
+
+snapshots["vaadin-breadcrumb-item item shadow last item with href"] =
+`
+
+
+
+
+
+`;
+/* end snapshot vaadin-breadcrumb-item item shadow last item with href */
+
+snapshots["vaadin-breadcrumb-item item shadow last item without href"] =
+`
+
+
+
+
+
+`;
+/* end snapshot vaadin-breadcrumb-item item shadow last item without href */
+
diff --git a/packages/breadcrumb/test/dom/breadcrumb.test.js b/packages/breadcrumb/test/dom/breadcrumb.test.js
new file mode 100644
index 00000000000..c0c8a02f591
--- /dev/null
+++ b/packages/breadcrumb/test/dom/breadcrumb.test.js
@@ -0,0 +1,150 @@
+import { expect } from '@vaadin/chai-plugins';
+import { sendKeys } from '@vaadin/test-runner-commands';
+import { fixtureSync } from '@vaadin/testing-helpers';
+
+// Enable experimental breadcrumb component
+window.Vaadin ??= {};
+window.Vaadin.featureFlags ??= {};
+window.Vaadin.featureFlags.breadcrumbComponent = true;
+
+import '../../src/vaadin-breadcrumb.js';
+import '../../src/vaadin-breadcrumb-item.js';
+
+describe('vaadin-breadcrumb', () => {
+ let breadcrumb;
+
+ describe('breadcrumb host', () => {
+ beforeEach(() => {
+ breadcrumb = fixtureSync(`
+
+ Home
+ Products
+ Current Page
+
+ `);
+ });
+
+ it('default', async () => {
+ await expect(breadcrumb).dom.to.equalSnapshot();
+ });
+
+ it('focused', async () => {
+ breadcrumb.querySelector('vaadin-breadcrumb-item').focus();
+ await expect(breadcrumb).dom.to.equalSnapshot();
+ });
+
+ it('focus-ring', async () => {
+ await sendKeys({ press: 'Tab' });
+ await expect(breadcrumb).dom.to.equalSnapshot();
+ });
+ });
+
+ describe('breadcrumb shadow', () => {
+ beforeEach(() => {
+ breadcrumb = fixtureSync(`
+
+ Home
+ Products
+ Current Page
+
+ `);
+ });
+
+ it('default', async () => {
+ await expect(breadcrumb).shadowDom.to.equalSnapshot();
+ });
+ });
+});
+
+describe('vaadin-breadcrumb-item', () => {
+ let item;
+
+ describe('item host', () => {
+ it('default with href', async () => {
+ item = fixtureSync('Products');
+ await expect(item).dom.to.equalSnapshot();
+ });
+
+ it('default without href', async () => {
+ item = fixtureSync('Current Page');
+ await expect(item).dom.to.equalSnapshot();
+ });
+
+ it('disabled', async () => {
+ item = fixtureSync('Products');
+ await expect(item).dom.to.equalSnapshot();
+ });
+
+ it('with target', async () => {
+ item = fixtureSync(
+ 'Vaadin',
+ );
+ await expect(item).dom.to.equalSnapshot();
+ });
+
+ it('router-ignore', async () => {
+ item = fixtureSync('API');
+ await expect(item).dom.to.equalSnapshot();
+ });
+
+ it('focused', async () => {
+ item = fixtureSync('Products');
+ item.focus();
+ await expect(item).dom.to.equalSnapshot();
+ });
+
+ it('focus-ring', async () => {
+ item = fixtureSync('Products');
+ await sendKeys({ press: 'Tab' });
+ await expect(item).dom.to.equalSnapshot();
+ });
+
+ it('last item with href', async () => {
+ item = fixtureSync('Products');
+ item._setLast(true);
+ await expect(item).dom.to.equalSnapshot();
+ });
+
+ it('last item without href', async () => {
+ item = fixtureSync('Current Page');
+ item._setLast(true);
+ await expect(item).dom.to.equalSnapshot();
+ });
+
+ it('current', async () => {
+ item = fixtureSync('Home');
+ // Simulate matching current page
+ item._setCurrent(true);
+ await expect(item).dom.to.equalSnapshot();
+ });
+ });
+
+ describe('item shadow', () => {
+ it('default with href', async () => {
+ item = fixtureSync('Products');
+ await expect(item).shadowDom.to.equalSnapshot();
+ });
+
+ it('default without href', async () => {
+ item = fixtureSync('Current Page');
+ await expect(item).shadowDom.to.equalSnapshot();
+ });
+
+ it('disabled', async () => {
+ item = fixtureSync('Products');
+ await expect(item).shadowDom.to.equalSnapshot();
+ });
+
+ it('last item with href', async () => {
+ item = fixtureSync('Products');
+ item._setLast(true);
+ await expect(item).shadowDom.to.equalSnapshot();
+ });
+
+ it('last item without href', async () => {
+ item = fixtureSync('Current Page');
+ item._setLast(true);
+ await expect(item).shadowDom.to.equalSnapshot();
+ });
+ });
+});
diff --git a/packages/breadcrumb/test/visual/base/breadcrumb.test.js b/packages/breadcrumb/test/visual/base/breadcrumb.test.js
new file mode 100644
index 00000000000..7a3670b2766
--- /dev/null
+++ b/packages/breadcrumb/test/visual/base/breadcrumb.test.js
@@ -0,0 +1,210 @@
+import { resetMouse, sendKeys, sendMouseToElement } from '@vaadin/test-runner-commands';
+import { fixtureSync } from '@vaadin/testing-helpers';
+import { visualDiff } from '@web/test-runner-visual-regression';
+import '../../../src/vaadin-breadcrumb.js';
+import '../../../src/vaadin-breadcrumb-item.js';
+
+describe('breadcrumb', () => {
+ let div, element;
+
+ afterEach(async () => {
+ await resetMouse();
+ });
+
+ describe('basic', () => {
+ it('default', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Products
+ Laptops
+ MacBook Pro
+ `,
+ div,
+ );
+ await visualDiff(div, 'default');
+ });
+
+ it('short', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Settings
+ `,
+ div,
+ );
+ await visualDiff(div, 'short');
+ });
+
+ it('single-item', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Current Page
+ `,
+ div,
+ );
+ await visualDiff(div, 'single-item');
+ });
+
+ it('long-trail', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Documentation
+ Components
+ Navigation
+ Breadcrumb
+ Examples
+ `,
+ div,
+ );
+ await visualDiff(div, 'long-trail');
+ });
+ });
+
+ describe('states', () => {
+ it('hover', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Products
+ Current Page
+ `,
+ div,
+ );
+ const item = element.querySelector('vaadin-breadcrumb-item[href="/products"]');
+ await sendMouseToElement({ type: 'move', element: item });
+ await visualDiff(div, 'hover');
+ });
+
+ it('focus-ring', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Products
+ Current Page
+ `,
+ div,
+ );
+ await sendKeys({ press: 'Tab' });
+ await visualDiff(div, 'focus-ring-first');
+ });
+
+ it('focus-ring-second', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Products
+ Current Page
+ `,
+ div,
+ );
+ await sendKeys({ press: 'Tab' });
+ await sendKeys({ press: 'Tab' });
+ await visualDiff(div, 'focus-ring-second');
+ });
+
+ it('disabled', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Products
+ Current Page
+ `,
+ div,
+ );
+ await visualDiff(div, 'disabled');
+ });
+
+ it('disabled-hover', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Products
+ Current Page
+ `,
+ div,
+ );
+ const item = element.querySelector('vaadin-breadcrumb-item[disabled]');
+ await sendMouseToElement({ type: 'move', element: item });
+ await visualDiff(div, 'disabled-hover');
+ });
+ });
+
+ describe('attributes', () => {
+ it('with-target', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Vaadin (opens in new tab)
+ Current Page
+ `,
+ div,
+ );
+ await visualDiff(div, 'with-target');
+ });
+
+ it('router-ignore', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ API (full page reload)
+ Current
+ `,
+ div,
+ );
+ await visualDiff(div, 'router-ignore');
+ });
+ });
+
+ describe('RTL', () => {
+ it('rtl', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ div.setAttribute('dir', 'rtl');
+ element = fixtureSync(
+ `
+ الصفحة الرئيسية
+ المنتجات
+ الصفحة الحالية
+ `,
+ div,
+ );
+ await visualDiff(div, 'rtl');
+ });
+ });
+});
diff --git a/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/default.png b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/default.png
new file mode 100644
index 00000000000..17f049bf7ea
Binary files /dev/null and b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/default.png differ
diff --git a/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/disabled-hover.png b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/disabled-hover.png
new file mode 100644
index 00000000000..0871cc70ecc
Binary files /dev/null and b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/disabled-hover.png differ
diff --git a/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/disabled.png b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/disabled.png
new file mode 100644
index 00000000000..0871cc70ecc
Binary files /dev/null and b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/disabled.png differ
diff --git a/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/focus-ring-first.png b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/focus-ring-first.png
new file mode 100644
index 00000000000..0871cc70ecc
Binary files /dev/null and b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/focus-ring-first.png differ
diff --git a/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/focus-ring-second.png b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/focus-ring-second.png
new file mode 100644
index 00000000000..0871cc70ecc
Binary files /dev/null and b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/focus-ring-second.png differ
diff --git a/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/hover.png b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/hover.png
new file mode 100644
index 00000000000..0871cc70ecc
Binary files /dev/null and b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/hover.png differ
diff --git a/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/long-trail.png b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/long-trail.png
new file mode 100644
index 00000000000..1be32b3501a
Binary files /dev/null and b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/long-trail.png differ
diff --git a/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/router-ignore.png b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/router-ignore.png
new file mode 100644
index 00000000000..f8f24302645
Binary files /dev/null and b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/router-ignore.png differ
diff --git a/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/rtl.png b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/rtl.png
new file mode 100644
index 00000000000..7fde199bb15
Binary files /dev/null and b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/rtl.png differ
diff --git a/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/short.png b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/short.png
new file mode 100644
index 00000000000..67ee48d1aec
Binary files /dev/null and b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/short.png differ
diff --git a/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/single-item.png b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/single-item.png
new file mode 100644
index 00000000000..be6bd69fea0
Binary files /dev/null and b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/single-item.png differ
diff --git a/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/with-target.png b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/with-target.png
new file mode 100644
index 00000000000..2a100f8e191
Binary files /dev/null and b/packages/breadcrumb/test/visual/base/screenshots/breadcrumb/baseline/with-target.png differ
diff --git a/packages/breadcrumb/test/visual/lumo/breadcrumb.test.js b/packages/breadcrumb/test/visual/lumo/breadcrumb.test.js
new file mode 100644
index 00000000000..c6ee7d1a280
--- /dev/null
+++ b/packages/breadcrumb/test/visual/lumo/breadcrumb.test.js
@@ -0,0 +1,217 @@
+import { resetMouse, sendKeys, sendMouseToElement } from '@vaadin/test-runner-commands';
+import { fixtureSync } from '@vaadin/testing-helpers';
+import { visualDiff } from '@web/test-runner-visual-regression';
+import '@vaadin/vaadin-lumo-styles/props.css';
+
+// Enable experimental breadcrumb component
+window.Vaadin ??= {};
+window.Vaadin.featureFlags ??= {};
+window.Vaadin.featureFlags.breadcrumbComponent = true;
+
+import '../../../vaadin-breadcrumb.js';
+import '../../../vaadin-breadcrumb-item.js';
+
+describe('breadcrumb', () => {
+ let div, element;
+
+ afterEach(async () => {
+ await resetMouse();
+ });
+
+ describe('basic', () => {
+ it('default', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Products
+ Laptops
+ MacBook Pro
+ `,
+ div,
+ );
+ await visualDiff(div, 'default');
+ });
+
+ it('short', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Settings
+ `,
+ div,
+ );
+ await visualDiff(div, 'short');
+ });
+
+ it('single-item', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Current Page
+ `,
+ div,
+ );
+ await visualDiff(div, 'single-item');
+ });
+
+ it('long-trail', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Documentation
+ Components
+ Navigation
+ Breadcrumb
+ Examples
+ `,
+ div,
+ );
+ await visualDiff(div, 'long-trail');
+ });
+ });
+
+ describe('states', () => {
+ it('hover', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Products
+ Current Page
+ `,
+ div,
+ );
+ const item = element.querySelector('vaadin-breadcrumb-item[href="/products"]');
+ await sendMouseToElement({ type: 'move', element: item });
+ await visualDiff(div, 'hover');
+ });
+
+ it('focus-ring', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Products
+ Current Page
+ `,
+ div,
+ );
+ await sendKeys({ press: 'Tab' });
+ await visualDiff(div, 'focus-ring-first');
+ });
+
+ it('focus-ring-second', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Products
+ Current Page
+ `,
+ div,
+ );
+ await sendKeys({ press: 'Tab' });
+ await sendKeys({ press: 'Tab' });
+ await visualDiff(div, 'focus-ring-second');
+ });
+
+ it('disabled', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Products
+ Current Page
+ `,
+ div,
+ );
+ await visualDiff(div, 'disabled');
+ });
+
+ it('disabled-hover', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Products
+ Current Page
+ `,
+ div,
+ );
+ const item = element.querySelector('vaadin-breadcrumb-item[disabled]');
+ await sendMouseToElement({ type: 'move', element: item });
+ await visualDiff(div, 'disabled-hover');
+ });
+ });
+
+ describe('attributes', () => {
+ it('with-target', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ Vaadin (opens in new tab)
+ Current Page
+ `,
+ div,
+ );
+ await visualDiff(div, 'with-target');
+ });
+
+ it('router-ignore', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ element = fixtureSync(
+ `
+ Home
+ API (full page reload)
+ Current
+ `,
+ div,
+ );
+ await visualDiff(div, 'router-ignore');
+ });
+ });
+
+ describe('RTL', () => {
+ it('rtl', async () => {
+ div = document.createElement('div');
+ div.style.display = 'inline-block';
+ div.style.padding = '10px';
+ div.setAttribute('dir', 'rtl');
+ element = fixtureSync(
+ `
+ الصفحة الرئيسية
+ المنتجات
+ الصفحة الحالية
+ `,
+ div,
+ );
+ await visualDiff(div, 'rtl');
+ });
+ });
+});
diff --git a/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/default.png b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/default.png
new file mode 100644
index 00000000000..68ee49bdb7e
Binary files /dev/null and b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/default.png differ
diff --git a/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/disabled-hover.png b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/disabled-hover.png
new file mode 100644
index 00000000000..434bef30911
Binary files /dev/null and b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/disabled-hover.png differ
diff --git a/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/disabled.png b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/disabled.png
new file mode 100644
index 00000000000..434bef30911
Binary files /dev/null and b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/disabled.png differ
diff --git a/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/focus-ring-first.png b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/focus-ring-first.png
new file mode 100644
index 00000000000..434bef30911
Binary files /dev/null and b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/focus-ring-first.png differ
diff --git a/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/focus-ring-second.png b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/focus-ring-second.png
new file mode 100644
index 00000000000..434bef30911
Binary files /dev/null and b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/focus-ring-second.png differ
diff --git a/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/hover.png b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/hover.png
new file mode 100644
index 00000000000..c9e3e86ce0c
Binary files /dev/null and b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/hover.png differ
diff --git a/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/long-trail.png b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/long-trail.png
new file mode 100644
index 00000000000..fb32a1f5eae
Binary files /dev/null and b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/long-trail.png differ
diff --git a/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/router-ignore.png b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/router-ignore.png
new file mode 100644
index 00000000000..ad43bad9f19
Binary files /dev/null and b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/router-ignore.png differ
diff --git a/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/rtl.png b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/rtl.png
new file mode 100644
index 00000000000..d351e526fb1
Binary files /dev/null and b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/rtl.png differ
diff --git a/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/short.png b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/short.png
new file mode 100644
index 00000000000..4c9fcf76013
Binary files /dev/null and b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/short.png differ
diff --git a/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/single-item.png b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/single-item.png
new file mode 100644
index 00000000000..6007c6d26ce
Binary files /dev/null and b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/single-item.png differ
diff --git a/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/with-target.png b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/with-target.png
new file mode 100644
index 00000000000..1b41aed6dde
Binary files /dev/null and b/packages/breadcrumb/test/visual/lumo/screenshots/breadcrumb/baseline/with-target.png differ
diff --git a/packages/breadcrumb/vaadin-breadcrumb-item.d.ts b/packages/breadcrumb/vaadin-breadcrumb-item.d.ts
new file mode 100644
index 00000000000..5af55d239ca
--- /dev/null
+++ b/packages/breadcrumb/vaadin-breadcrumb-item.d.ts
@@ -0,0 +1 @@
+export * from './src/vaadin-breadcrumb-item.js';
diff --git a/packages/breadcrumb/vaadin-breadcrumb-item.js b/packages/breadcrumb/vaadin-breadcrumb-item.js
new file mode 100644
index 00000000000..26b3617b7b2
--- /dev/null
+++ b/packages/breadcrumb/vaadin-breadcrumb-item.js
@@ -0,0 +1,6 @@
+/**
+ * @license
+ * Copyright (c) 2017 - 2025 Vaadin Ltd.
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
+ */
+export { BreadcrumbItem } from './src/vaadin-breadcrumb-item.js';
diff --git a/packages/breadcrumb/vaadin-breadcrumb.d.ts b/packages/breadcrumb/vaadin-breadcrumb.d.ts
new file mode 100644
index 00000000000..c6198ecdd2f
--- /dev/null
+++ b/packages/breadcrumb/vaadin-breadcrumb.d.ts
@@ -0,0 +1 @@
+export * from './src/vaadin-breadcrumb.js';
diff --git a/packages/breadcrumb/vaadin-breadcrumb.js b/packages/breadcrumb/vaadin-breadcrumb.js
new file mode 100644
index 00000000000..1aca182f729
--- /dev/null
+++ b/packages/breadcrumb/vaadin-breadcrumb.js
@@ -0,0 +1,6 @@
+/**
+ * @license
+ * Copyright (c) 2017 - 2025 Vaadin Ltd.
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
+ */
+export { Breadcrumb } from './src/vaadin-breadcrumb.js';