into a Vanilla Breeze page — syntax-highlighted code with copy, line numbers, focus mode, diff support, and multi-file tabs. Already a VB dependency."> <!-- Canonical URL --><link rel="canonical" href="https://profpowell.github.io/vanilla-breeze/docs/integrations/web-components/code-block/"> <!-- Open Graph / Facebook --><meta property="og:type" content="website"><meta property="og:url" content="https://profpowell.github.io/vanilla-breeze/docs/integrations/web-components/code-block/"><meta property="og:title" content="code-block integration | Vanilla Breeze"><meta property="og:description" content="Drop a <code-block> into a Vanilla Breeze page — syntax-highlighted code with copy, line numbers, focus mode, diff support, and multi-file tabs. Already a VB dependency."><meta property="og:image" content="https://profpowell.github.io/vanilla-breeze/og-image.png"> <!-- Twitter --><meta property="twitter:card" content="summary_large_image"><meta property="twitter:url" content="https://profpowell.github.io/vanilla-breeze/docs/integrations/web-components/code-block/"><meta property="twitter:title" content="code-block integration | Vanilla Breeze"><meta property="twitter:description" content="Drop a <code-block> into a Vanilla Breeze page — syntax-highlighted code with copy, line numbers, focus mode, diff support, and multi-file tabs. Already a VB dependency."><meta property="twitter:image" content="https://profpowell.github.io/vanilla-breeze/og-image.png"> <!-- Theme: prevent flash of wrong theme --><!-- FOUC guard: hide body while a non-core brand theme is still loading. The inline boot script below may document.write a <link rel="stylesheet" data-vb-theme-state="loading"> for the user's saved theme. With cross-document View Transitions enabled (@view-transition in view-transitions.css), the new page's first paint can happen before that stylesheet arrives, producing a one-frame flash in default theme before the user's real theme repaints. Hiding the body via :has() on the loading attribute keeps paint suppressed until the onload handler flips state → "ready" (or onerror → "error"), at which point this selector no longer matches and body becomes visible. --><style> html:has(link[data-vb-theme-state="loading"]) body { visibility: hidden; }</style> <script data-vb-is-dev="false"> try { // Read VBStore-format keys: `vb:{namespace}:{key}` with an envelope // { data, timestamp }. The boot used to read simple keys like // 'vb-theme' — those were never written by any code (theme-picker // and settings-panel both persist through VBStore), so every // localStorage.getItem returned null, the boot fell through to // the default theme, and ThemeManager.init() belatedly applied // the real brand — producing the visible flash from default → // stored theme on every page load. const vbRead = (ns, key) => { try { const raw = localStorage.getItem(`vb:${ns}:${key}`); if (!raw) return null; const env = JSON.parse(raw); return env && typeof env === 'object' && 'data' in env ? env.data : null; } catch { return null; } }; const stored = vbRead('theme', 'current') || {}; const mode = stored.mode || 'auto'; const brand = stored.brand || 'default'; const isDev = document.currentScript?.dataset.vbIsDev === 'true'; const coreThemes = new Set([ 'default', 'a11y-high-contrast', 'a11y-large-text', 'a11y-dyslexia', 'modern', 'minimal', 'classic' ]); const packThemes = new Set(['kawaii', 'memphis']); if (mode !== 'auto') { document.documentElement.dataset.mode = mode; } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.dataset.mode = 'dark'; } const a11y = vbRead('settings', 'a11y') || []; const parts = []; if (brand && brand !== 'default') parts.push(brand); parts.push(...(Array.isArray(a11y) ? a11y : [])); if (parts.length) document.documentElement.dataset.theme = parts.join(' '); const safeBrand = /^[a-z0-9-]+$/.test(brand) ? brand : 'default'; if (!isDev && safeBrand && !coreThemes.has(safeBrand) && !document.querySelector(`link[data-vb-theme="${safeBrand}"]`)) { const href = `/cdn/themes/${safeBrand}.css`; const packAttr = packThemes.has(safeBrand) ? ` data-vb-pack="${safeBrand}"` : ''; document.write( `<link rel="stylesheet" href="${href}" data-vb-theme="${safeBrand}" data-vb-theme-state="loading" data-vb-theme-boot=""${packAttr} onload="this.setAttribute('data-vb-theme-state','ready')" onerror="this.setAttribute('data-vb-theme-state','error')">` ); // Safety fallback: if neither onload nor onerror fires within 1.5s // (slow network, broken cache, …), flip the state anyway so the // :has(loading) FOUC guard above doesn't leave the page invisible. setTimeout(() => { const link = document.querySelector('link[data-vb-theme-boot]'); if (link && link.getAttribute('data-vb-theme-state') === 'loading') { link.setAttribute('data-vb-theme-state', 'timeout'); } }, 1500); } if (stored.fluid) document.documentElement.dataset.fluid = stored.fluid; const effectiveMode = mode !== 'auto' ? mode : window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; const themeColor = effectiveMode === 'dark' ? '#1a1a1a' : '#ffffff'; document.querySelectorAll('meta[name="theme-color"]').forEach(m => { m.removeAttribute('media'); }); document.querySelector('meta[name="theme-color"]').content = themeColor; const ext = vbRead('settings', 'extensions') || {}; if (ext.motionFx === false) document.documentElement.dataset.motionReduced = ''; if (vbRead('settings', 'sticky') === 'on') document.documentElement.dataset.sticky = ''; } catch(e) {}</script> <meta name="vb-service-worker" content="true"> <!-- Analytics config (read by src/lib/analytics.js when vanilla-breeze-core.js boots). Transport 'beacon' POSTs to /api/analytics/*. Use 'console' to log events to devtools instead, or 'disabled' to turn analytics off entirely. Local dev (*.test, localhost, 127.0.0.1) has no analytics Worker, so the transport drops to 'disabled' to avoid 404 spam. Flip to 'console' in devtools via `vbAnalyticsConfig.transport = 'console'` before reload to inspect events locally. urlMasks collapses the /stats/?site=…&window=… query-string variants into a single canonical /stats/ path so the Top Pages panel doesn't fragment across every filter combination. See /docs/analytics/#url-masking. --><script> (function () { const host = location.hostname; const isLocal = host === 'localhost' || host === '127.0.0.1' || host.endsWith('.test'); window.vbAnalyticsConfig = { siteId: 'vb-docs', transport: isLocal ? 'disabled' : 'beacon', endpoint: '/api/analytics', urlMasks: [ { pattern: /^\/stats\/\?.*$/, replace: '/stats/' }, ], }; })();</script> <!-- Main CSS --><link rel="stylesheet" href="/cdn/vanilla-breeze-core.css"><link rel="stylesheet" href="/cdn/packs/fonts-foundation.theme.css"><link rel="stylesheet" href="/cdn/packs/ui.full.css"> <!-- Glossary popover (definition-popover.js / .css are emitted by site/plugins/generate-definitions-json.js into dist/pages/{js,css}/) --><link rel="stylesheet" href="/css/definition-popover.css"><script src="/js/definition-popover.js" defer></script> <!-- Topic index sort + shared topic styles (emitted by site/plugins/generate-topic-assets.js). topic-sort.js early-exits when the page has no [data-topic-index]. --><link rel="stylesheet" href="/css/topic-index.css"><script src="/js/topic-sort.js" defer></script> <link rel="stylesheet" href="/docs/docs.css"> <meta property="og:type" content="article"> </head><body style="overflow-x: clip;"> <header class="site" data-sticky="" data-vt="header"> <a href="/"><brand-mark>Vanilla Breeze</brand-mark></a> <nav-bar aria-label="Main navigation"> <nav class="horizontal pills"> <ul> <li> <drop-down position="bottom-start" hover=""> <a href="/docs/quick-start/" data-trigger="" data-nav-link="" data-nav-section="start">Start</a> <menu> <li><a href="/docs/quick-start/">Quick Start</a></li> <li><a href="/docs/tutorial/">Tutorial</a></li> <li><a href="/docs/principles/">Principles</a></li> </menu> </drop-down> </li> <li> <drop-down position="bottom-start" hover=""> <a href="/docs/semantic-layouts/" data-trigger="" data-nav-link="" data-nav-section="guide">Guide</a> <menu> <li><a href="/docs/semantic-layouts/">Layouts</a></li> <li><a href="/docs/typography/">Styling</a></li> <li><a href="/docs/accessibility/">Accessibility</a></li> <li><a href="/docs/mobile/">Platform</a></li> <li><a href="/docs/concepts/">Concepts</a></li> </menu> </drop-down> </li> <li> <drop-down position="bottom-start" hover=""> <a href="/docs/elements/" data-trigger="" data-nav-link="" data-nav-section="api">API</a> <menu> <li><a href="/docs/elements/">Elements</a></li> <li><a href="/docs/attributes/">Attributes</a></li> <li><a href="/docs/tokens/">Tokens</a></li> <li><a href="/docs/themes/">Themes</a></li> <li><a href="/docs/packs/">Packs</a></li> </menu> </drop-down> </li> <li> <drop-down position="bottom-start" hover=""> <a href="/docs/snippets/" data-trigger="" data-nav-link="" data-nav-section="explore">Explore</a> <menu> <li><a href="/docs/snippets/">Snippets</a></li> <li><a href="/docs/patterns/">Patterns</a></li> <li><a href="/docs/examples/">Demos</a></li> <li><a href="/docs/tools/">Tools</a></li> <li><a class="active-parent" href="/docs/integrations/">Integrations</a></li> </menu> </drop-down> </li> <li> <drop-down position="bottom-start" hover=""> <a href="/changelog/" data-trigger="" data-nav-link="" data-nav-section="reference">Reference</a> <menu> <li><a href="/changelog/">Changelog</a></li> <li><a href="/glossary/">Glossary</a></li> <li><a href="/index-of/">Keyword Index</a></li> <li><a href="/sitemap/">Sitemap</a></li> </menu> </drop-down> </li> </ul> </nav> </nav-bar> <site-tools> <site-search> <button type="button" data-trigger="" class="ghost"> <icon-wc name="search" size="sm"></icon-wc> Search </button> </site-search> <notification-wc src="/go/notify/messages"> <article data-id="welcome" data-type="update" data-date="2026-04-21T00:00:00Z"> <h3>Welcome to Vanilla Breeze</h3> <p>This bell pulls live notifications from <code>/go/notify/messages</code> — the same contract documented at <a href="/docs/concepts/service-contracts/">/docs/concepts/service-contracts/</a>. Static articles like this one are the no-JS / no-backend fallback.</p> <a href="/docs/concepts/go-convention/">Read the /go/ convention</a> </article> </notification-wc> <theme-picker compact=""> <button type="button" data-trigger="" class="ghost"> <icon-wc name="palette" size="sm"></icon-wc> Theme </button> </theme-picker> </site-tools> <mobile-menu breakpoint="52rem"> <button type="button" data-trigger="" popovertarget="mobile-menu" aria-label="Menu"> <icon-wc name="menu"></icon-wc> </button> <nav popover="" id="mobile-menu" class="mobile-menu-panel"> <button type="button" class="mobile-menu-close" popovertarget="mobile-menu" popovertargetaction="hide" aria-label="Close menu"> <icon-wc name="x"></icon-wc> </button> <div class="mobile-menu-search"> <site-search> <button type="button" data-trigger="" class="ghost" style="inline-size: 100%"> <icon-wc name="search" size="sm"></icon-wc> Search </button> </site-search> </div> <ul> <li class="mobile-section"> <strong class="mobile-section-title">Start</strong> <ul> <li><a href="/docs/quick-start/">Quick Start</a></li> <li><a href="/docs/tutorial/">Tutorial</a></li> <li><a href="/docs/principles/">Principles</a></li> </ul> </li> <li class="mobile-section"> <strong class="mobile-section-title">Guide</strong> <ul> <li><a href="/docs/semantic-layouts/">Layouts</a></li> <li><a href="/docs/typography/">Styling</a></li> <li><a href="/docs/accessibility/">Accessibility</a></li> <li><a href="/docs/mobile/">Platform</a></li> <li><a href="/docs/concepts/">Concepts</a></li> </ul> </li> <li class="mobile-section"> <strong class="mobile-section-title">API</strong> <ul> <li><a href="/docs/elements/">Elements</a></li> <li><a href="/docs/attributes/">Attributes</a></li> <li><a href="/docs/tokens/">Tokens</a></li> <li><a href="/docs/themes/">Themes</a></li> <li><a href="/docs/packs/">Packs</a></li> </ul> </li> <li class="mobile-section"> <strong class="mobile-section-title">Explore</strong> <ul> <li><a href="/docs/snippets/">Snippets</a></li> <li><a href="/docs/patterns/">Patterns</a></li> <li><a href="/docs/examples/">Demos</a></li> <li><a href="/docs/tools/">Tools</a></li> <li><a class="active-parent" href="/docs/integrations/">Integrations</a></li> </ul> </li> <li class="mobile-section"> <strong class="mobile-section-title">Reference</strong> <ul> <li><a href="/changelog/">Changelog</a></li> <li><a href="/glossary/">Glossary</a></li> <li><a href="/index-of/">Keyword Index</a></li> <li><a href="/sitemap/">Sitemap</a></li> </ul> </li> </ul> <div class="mobile-menu-theme"> <strong class="mobile-section-title">Theme</strong> <theme-picker variant="inline" compact=""></theme-picker> </div> </nav> </mobile-menu></header> <main data-pagefind-body="" style="overflow-x: clip;"> <nav class="breadcrumb" aria-label="Breadcrumb"> <ol> <li><a class="active-parent" href="/docs/integrations/">Integrations</a></li> <li><a class="active-parent" href="/docs/integrations/web-components/">Web Components</a></li> <li><span aria-current="page">code-block</span></li> </ol></nav> <h1>code-block</h1><p class="lead">A syntax-highlighted code block with copy-to-clipboard, line numbers, focus mode, diff support, and multi-file tabs. Already used throughout these docs and shipped as a VB dependency, but independently usable on any HTML page.</p> <section> <h2>Where to find it</h2> <ul> <li>npm: <a class="active" href="https://www.npmjs.com/package/@profpowell/code-block"><code>@profpowell/code-block</code></a></li> <li>GitHub: <a class="active" href="https://github.com/ProfPowell/code-block">ProfPowell/code-block</a></li> <li>Live demo: <a class="active" href="https://profpowell.github.io/code-block/">profpowell.github.io/code-block</a></li> <li>License: MIT</li> </ul></section> <section> <h2>Install</h2> <code-block language="bash">npm install @profpowell/code-block