diff --git a/packages/renderer-parity.json b/packages/renderer-parity.json
index 6d2a6cfa2..eef52c67f 100644
--- a/packages/renderer-parity.json
+++ b/packages/renderer-parity.json
@@ -55,6 +55,7 @@
"core/post-terms",
"core/read-more",
"core/term-description",
+ "artisanpack/breadcrumbs",
"artisanpack/callout",
"artisanpack/paragraph",
"artisanpack/heading",
diff --git a/packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/breadcrumbs.blade.php b/packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/breadcrumbs.blade.php
new file mode 100644
index 000000000..2f26e42f4
--- /dev/null
+++ b/packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/breadcrumbs.blade.php
@@ -0,0 +1,86 @@
+@php
+ use ArtisanPackUI\VisualEditorRendererBlade\Support\BlockSupports;
+ use ArtisanPackUI\VisualEditorRendererBlade\Support\UrlSanitizer;
+
+ $validSeparators = [ 'arrow-right', 'chevron-right', 'chevron-double-right', 'long-arrow-right' ];
+
+ $separatorIcon = (string) ( $attributes['separatorIcon'] ?? 'chevron-right' );
+
+ if ( ! in_array( $separatorIcon, $validSeparators, true ) ) {
+ $separatorIcon = 'chevron-right';
+ }
+
+ $separatorPaths = [
+ 'arrow-right' => 'M5 12h14m-6-6 6 6-6 6',
+ 'chevron-right' => 'm9 6 6 6-6 6',
+ 'chevron-double-right' => 'm6 6 6 6-6 6m6-12 6 6-6 6',
+ 'long-arrow-right' => 'M3 12h17m-5-5 5 5-5 5',
+ ];
+ $separatorPath = $separatorPaths[ $separatorIcon ];
+
+ $breadcrumbsSchema = (bool) ( $attributes['breadcrumbsSchema'] ?? true );
+
+ $trailRaw = $attributes['_resolvedTrail'] ?? [];
+ $trail = [];
+
+ if ( is_array( $trailRaw ) ) {
+ foreach ( $trailRaw as $entry ) {
+ if ( ! is_array( $entry ) ) {
+ continue;
+ }
+
+ $label = (string) ( $entry['label'] ?? '' );
+
+ if ( '' === $label ) {
+ continue;
+ }
+
+ $rawUrl = isset( $entry['url'] ) ? (string) $entry['url'] : '';
+ $sanitizedUrl = '' === $rawUrl ? '' : UrlSanitizer::safe( $rawUrl );
+
+ $trail[] = [
+ 'label' => $label,
+ 'url' => '' === $sanitizedUrl ? null : $sanitizedUrl,
+ 'current' => ! empty( $entry['current'] ),
+ ];
+ }
+ }
+
+ $ariaLabel = (string) ( $attributes['ariaLabel'] ?? 'Breadcrumb' );
+
+ $baseClasses = [ 'ap-breadcrumbs' ];
+
+ $listAttrs = $breadcrumbsSchema ? ' itemscope itemtype="https://schema.org/BreadcrumbList"' : '';
+ $listItemAttrs = $breadcrumbsSchema ? ' itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem"' : '';
+@endphp
+
diff --git a/packages/visual-editor-renderer-blade/tests/Unit/BlockRendererTest.php b/packages/visual-editor-renderer-blade/tests/Unit/BlockRendererTest.php
index f802a0b59..76158c137 100644
--- a/packages/visual-editor-renderer-blade/tests/Unit/BlockRendererTest.php
+++ b/packages/visual-editor-renderer-blade/tests/Unit/BlockRendererTest.php
@@ -526,6 +526,89 @@ public function render( array $attrs )
->and( $html )->toContain( 'data-severity="info"' );
} );
+it( 'renders the artisanpack/breadcrumbs block with a resolved trail and schema microdata', function () {
+ $tree = [
+ makeBlock( 'artisanpack/breadcrumbs', [
+ 'separatorIcon' => 'chevron-right',
+ 'breadcrumbsSchema' => true,
+ '_resolvedTrail' => [
+ [ 'label' => 'Home', 'url' => '/' ],
+ [ 'label' => 'Blog', 'url' => '/blog' ],
+ [ 'label' => 'Hello World', 'current' => true ],
+ ],
+ ] ),
+ ];
+
+ $html = makeRenderer()->render( $tree );
+
+ expect( $html )->toContain( '
+ );
+}
diff --git a/packages/visual-editor-renderer-react/src/blocks/registerCoreBlocks.ts b/packages/visual-editor-renderer-react/src/blocks/registerCoreBlocks.ts
index c749da06e..e0b8ed6ff 100644
--- a/packages/visual-editor-renderer-react/src/blocks/registerCoreBlocks.ts
+++ b/packages/visual-editor-renderer-react/src/blocks/registerCoreBlocks.ts
@@ -12,6 +12,7 @@
*/
import { registerBlockRenderer } from '../registry';
+import { BreadcrumbsBlock } from './artisanpack/breadcrumbs';
import { CalloutBlock } from './artisanpack/callout';
import { LoginoutBlock } from './artisanpack/loginout';
import {
@@ -210,6 +211,7 @@ const CORE_BLOCKS: Record = {
'artisanpack/navigation': NavigationBlock,
'core/navigation-link': NavigationLinkBlock,
'core/navigation-submenu': NavigationSubmenuBlock,
+ 'artisanpack/breadcrumbs': BreadcrumbsBlock,
'artisanpack/callout': CalloutBlock,
// Post navigation / metadata family (#520) — same renderer for both
// namespaces; PostResolver stamps the same `_resolved*` attributes.
diff --git a/packages/visual-editor-renderer-react/tests/BlockTree.test.tsx b/packages/visual-editor-renderer-react/tests/BlockTree.test.tsx
index bcf94d55b..cd1f5e710 100644
--- a/packages/visual-editor-renderer-react/tests/BlockTree.test.tsx
+++ b/packages/visual-editor-renderer-react/tests/BlockTree.test.tsx
@@ -616,6 +616,82 @@ describe('Core design blocks', () => {
expect(html).toContain('ap-callout--info');
expect(html).toContain('data-severity="info"');
});
+
+ it('renders the artisanpack/breadcrumbs block with a resolved trail and schema microdata', () => {
+ const tree = [
+ makeBlock('artisanpack/breadcrumbs', {
+ separatorIcon: 'chevron-right',
+ breadcrumbsSchema: true,
+ _resolvedTrail: [
+ { label: 'Home', url: '/' },
+ { label: 'Blog', url: '/blog' },
+ { label: 'Hello World', current: true },
+ ],
+ }),
+ ];
+
+ const html = renderTree(tree);
+
+ expect(html).toContain('