Detroit-Style Web Component ๐:

Table of contents
- What Are Web Components? ๐ค
- Why Detroit-Style Pizza? ๐
- Let's Build Our Web Component! ๐งโ๐ณ
- Breaking Down the Recipe: Key Parts Explained ๐งฉ
- Using Our Pizza Component ๐ฝ๏ธ
- The Special Sauce: Why Web Components Are Awesome ๐
- When to Use Web Components ๐ค
- Final Slice: What We've Learned ๐ง

Hey coders! ๐ After slicing through JavaScript's .replace()
method in my last post, today I'm diving into the world of Web Components with a simple Detroit-style pizza visualization. I'll show you how to create a custom HTML element that renders a beautiful pizza with optional toppings.
What Are Web Components? ๐ค
Web Components allow you create reusable custom elements that work across any framework or no framework at all. Instead of reinventing your pizza oven each time you want to make a pizza, you build it once and use it everywhere!
Web Components use three core technologies:
Custom Elements API - Lets you define your own HTML tags
Shadow DOM - Creates an isolated DOM tree for your element
HTML Templates - Defines reusable markup templates
Today, we'll focus on creating a pizza component that shows off these key concepts with a delicious visual example.
Why Detroit-Style Pizza? ๐
Detroit-style pizza is the perfect metaphor for Web Components:
It has a distinctive rectangular shape (like our component's structure)
Features edge-to-edge cheese with caramelized edges (like our component's encapsulation)
Can be customized with various toppings (custom attributes), showing component configurability
Has its own unique style that stands out from other pizza types (like web components stand out from other UI approaches)
Plus, I'm on a pizza theme with my blog posts, so why stop now (Iโm in too deep)?
Let's Build Our Web Component! ๐งโ๐ณ
We're going to create a simple <detroit-pizza>
element that can be configured with HTML attributes. The special feature? Our pizza starts as a plain cheese pizza, with three optional additions you can enable with attributes:
Pepperoni (red)
Racing stripe sauce (deep red)
Fresh basil leaves (green)
Here's the full code:
/*
Detroit-style pizza web component
*/
class DetroitPizza extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
width: 400px;
}
.pizza {
width: 400px;
height: 225px;
background: #e8c38d;
border: 8px solid #c49c67;
border-radius: 4px;
position: relative;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
.cheese {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 5px solid #b26b14;
background: linear-gradient(45deg, #fddb6d, #fee9a0);
}
.sauce {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
}
/* three horizontal sauce stripes */
.sauce.racing-stripes::before,
.sauce.racing-stripes::after,
.sauce.racing-stripes .middle-stripe {
content: '';
position: absolute;
height: 12%;
left: 5%;
right: 5%;
border-radius: 20px;
background: #990000;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.sauce.racing-stripes::before {
top: 13%;
}
.sauce.racing-stripes::after {
bottom: 13%;
}
.sauce.racing-stripes .middle-stripe {
top: 50%;
transform: translateY(-50%);
}
/* toppings */
.toppings {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
}
.toppings.has-pepperoni {
background-image:
radial-gradient(#d92e0e 8px, transparent 9px);
background-size: 35px 35px;
background-position: 8px 8px;
background-position: 8px 8px;
padding: 3px;
box-sizing: border-box;
}
.basil-leaf {
position: absolute;
width: 20px;
height: 30px;
background: #386c0b;
border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
z-index: 5;
transform: rotate(15deg);
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.basil-leaf:nth-child(1) { top: 15%; left: 20%; transform: rotate(32deg); }
.basil-leaf:nth-child(2) { top: 65%; left: 30%; transform: rotate(-20deg); }
.basil-leaf:nth-child(3) { top: 25%; left: 75%; transform: rotate(10deg); }
.basil-leaf:nth-child(4) { top: 70%; left: 80%; transform: rotate(-35deg); }
.basil-leaf:nth-child(5) { top: 40%; left: 60%; transform: rotate(27deg); }
.basil-leaf:nth-child(6) { top: 75%; left: 45%; transform: rotate(15deg); }
.basil-leaf:nth-child(7) { top: 45%; left: 15%; transform: rotate(-12deg); }
.basil-leaf:nth-child(8) { top: 30%; left: 40%; transform: rotate(22deg); }
.basil-leaf::after {
content: '';
position: absolute;
top: 30%;
left: 20%;
width: 30%;
height: 15%;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
transform: rotate(-10deg);
}
</style>
<div class="pizza">
<div class="cheese"></div>
<div class="toppings"></div>
<div class="sauce">
<div class="middle-stripe"></div>
</div>
<div class="basil-container"></div>
</div>
`;
this.toppingsEl = this.shadowRoot.querySelector('.toppings');
this.sauceEl = this.shadowRoot.querySelector('.sauce');
this.basilContainer = this.shadowRoot.querySelector('.basil-container');
}
static get observedAttributes() {
return ['pepperoni', 'racing-stripes', 'basil'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'pepperoni') {
const hasPepperoni = newValue === 'true';
if (hasPepperoni) {
this.toppingsEl.classList.add('has-pepperoni');
} else {
this.toppingsEl.classList.remove('has-pepperoni');
}
}
if (name === 'racing-stripes') {
const hasStripes = newValue === 'true';
if (hasStripes) {
this.sauceEl.classList.add('racing-stripes');
} else {
this.sauceEl.classList.remove('racing-stripes');
}
}
if (name === 'basil') {
const hasBasil = newValue === 'true';
if (hasBasil) {
this.addBasilLeaves();
} else {
this.basilContainer.innerHTML = '';
}
}
}
addBasilLeaves() {
this.basilContainer.innerHTML = '';
for (let i = 1; i <= 8; i++) {
const leaf = document.createElement('div');
leaf.className = 'basil-leaf';
this.basilContainer.appendChild(leaf);
}
}
connectedCallback() {
if (!this.hasAttribute('pepperoni')) {
this.setAttribute('pepperoni', 'false');
}
if (!this.hasAttribute('racing-stripes')) {
this.setAttribute('racing-stripes', 'false');
}
if (!this.hasAttribute('basil')) {
this.setAttribute('basil', 'false');
}
}
}
customElements.define('detroit-pizza', DetroitPizza);
Breaking Down the Recipe: Key Parts Explained ๐งฉ
Let's explore each part of our component:
1. The Base: Extending HTMLElement
class DetroitPizza extends HTMLElement {
constructor() {
super();
Our component extends the built-in HTMLElement
class to get all the standard HTML element behavior. The super()
call initializes the base class.
2. The Secret Sauce: Shadow DOM
this.attachShadow({ mode: 'open' });
Shadow DOM creates a separate DOM tree for our component, keeping styles and structure encapsulated. Nothing leaks out, and outside styles don't mess up our pizza's appearance.
3. The Recipe: Styling and Structure
A golden-brown crust
A warm yellow cheese
Deep red sauce stripes (#990000)
Bright red pepperoni (#d92e0e)
Rich green basil leaves
4. The Opt-In Toppings: Strict Attribute Handling
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'pepperoni') {
const hasPepperoni = newValue === 'true';
// ...
}
}
We only add toppings when their respective attribute is exactly "true"
. Any other value keeps those features turned off. The basil leaves are nicely spread across the pizza at various angles for a natural look.
5. Default Plain Cheese: Initial Setup
connectedCallback() {
// All features off by default, enable with explicit "true"
if (!this.hasAttribute('pepperoni')) {
this.setAttribute('pepperoni', 'false');
}
// Same for other attributes...
}
When our component is added to the page, this method ensures it starts as a plain cheese pizza by setting attributes to "false" by default. Users must explicitly opt-in to get additional toppings.
Using Our Pizza Component ๐ฝ๏ธ
To use this component, include the JavaScript code and add the tag to your HTML. All features are off by default, so if you want them, you must explicitly enable them:
<!DOCTYPE html>
<html>
<head>
<title>Detroit Pizza Demo</title>
<script src="https://rawcdn.githack.com/DevManSam777/detroit-style-web-component/eea750ab212f4cc794e27ec87928d20a17b89afc/detroit-pizza.js"></script>
</head>
<body>
<h1>Detroit-Style Pizzas</h1>
<!-- Plain cheese pizza (default) -->
<h2>Plain Cheese</h2>
<detroit-pizza></detroit-pizza>
<!-- Pizza with pepperoni only -->
<h2>With Pepperoni</h2>
<detroit-pizza pepperoni="true"></detroit-pizza>
<!-- Pizza with racing stripes only -->
<h2>With Racing Stripes</h2>
<detroit-pizza racing-stripes="true"></detroit-pizza>
<!-- Pizza with basil only -->
<h2>With Fresh Basil</h2>
<detroit-pizza basil="true"></detroit-pizza>
<!-- The works - everything enabled -->
<h2>The Works</h2>
<detroit-pizza pepperoni="true" racing-stripes="true" basil="true"></detroit-pizza>
</body>
</html>
The Special Sauce: Why Web Components Are Awesome ๐
Web Components have several key advantages:
Framework-agnostic - They work in vanilla JavaScript or any framework
Encapsulated styling - CSS stays contained within the component
Declarative API - Configure through readable HTML attributes
Opt-in complexity - Start simple, add features only when needed
Future-proof - Built on web standards with long-term browser support
When to Use Web Components ๐ค
Web Components are particularly valuable for:
Creating design system elements that need to work consistently across projects
Building UI widgets with clean, attribute-based APIs
Developing embeddable content for third-party sites
Creating progressive enhancements for existing applications
Final Slice: What We've Learned ๐ง
In this tutorial, we've built a Detroit-style pizza web component that demonstrates several key principles:
Creating custom HTML elements
Using Shadow DOM for style encapsulation
Styling with pure CSS (no external images needed!)
Implementing opt-in features with strict attribute checking
The ability to start with a simple base and explicitly opt-in to additional features is a powerful pattern in web development. It follows the principle of progressive enhancement. We start with the essentials and let users add complexity only when needed.
Next time you're building a UI component, consider if the Web Components approach might be right for your project. They offer a great combination of encapsulation, reusability, and framework independence.
Are you using Web Components in your projects? Have you tried the opt-in approach to features? Drop a comment below and let me know!
Want to try it out?
Add this script element to the head section of your html page:
<script src="https://rawcdn.githack.com/DevManSam777/detroit-style-web-component/eea750ab212f4cc794e27ec87928d20a17b89afc/detroit-pizza.js"></script>
And add the component anywhere within your html body:
<detroit-pizza pepperoni="true" racing-stripes="true" basil="true"></detroit-pizza>
Until next time, happy coding and happy pizza-making! ๐๐ป
P.S. Follow me on bsky, or X/twitter, for more web development recipes with a dash of pizza-themed fun!
Subscribe to my newsletter
Read articles from Samir Shuman directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Samir Shuman
Samir Shuman
Welcome to my blog! My name is Samir. I'm a full-stack software engineer specializing in web development. I'm based in the San Francisco Bay Area, but can deliver digital solutions globally. When I'm not coding, I love experimenting in the kitchen, watching basketball, and spending time with my cat, Fuzzy. Let's connect!