JavaScript Shadow DOM & Web Components Basics Quiz
Answer 35 questions on custom element fundamentals, shadow DOM encapsulation, open vs closed roots, templates and slots, style isolation, lifecycle callbacks, reusable component design, and light/shadow DOM interaction.
Question 1
What is required before using a custom element tag in HTML?
Question 2
Why must custom element names contain a hyphen?
Question 3
What prints?
class MyButton extends HTMLElement {
connectedCallback() {
this.textContent = 'Click me'
}
}
customElements.define('my-button', MyButton)
const element = document.createElement('my-button')
console.log(element.textContent)
document.body.appendChild(element)
Question 4
Which lifecycle callback fires when an element is removed from the DOM?
Question 5
Why extend HTMLElement instead of HTMLDivElement?
Question 6
What is a shadow root?
Question 7
What prints?
class FancyLabel extends HTMLElement {
constructor() {
super()
const shadow = this.attachShadow({ mode: 'open' })
shadow.innerHTML = '<span>Shadow!</span>'
}
}
customElements.define('fancy-label', FancyLabel)
const el = document.createElement('fancy-label')
console.log(el.innerHTML)
document.body.appendChild(el)
console.log(el.shadowRoot.innerHTML)
Question 8
Why does shadow DOM provide style encapsulation by default?
Question 9
What is the difference between open and closed shadow roots?
Question 10
What prints?
class HiddenEl extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'closed' }).innerHTML = '<p>secret</p>'
}
}
customElements.define('hidden-el', HiddenEl)
const el = document.createElement('hidden-el')
console.log(el.shadowRoot)
Question 11
What prints?
const template = document.createElement('template')
template.innerHTML = '<style>span { color: red; }</style><slot></slot>'
class WarningLabel extends HTMLElement {
constructor() {
super()
const root = this.attachShadow({ mode: 'open' })
root.appendChild(template.content.cloneNode(true))
}
}
customElements.define('warning-label', WarningLabel)
const el = document.createElement('warning-label')
el.textContent = 'Careful'
document.body.appendChild(el)
console.log(getComputedStyle(el.shadowRoot.querySelector('span') ?? el).color)
Question 12
How do named slots work?
Question 13
What prints?
class StyledBox extends HTMLElement {
constructor() {
super()
const root = this.attachShadow({ mode: 'open' })
root.innerHTML = `<style>:host { display:block; border:1px solid black; }</style><slot></slot>`
}
}
customElements.define('styled-box', StyledBox)
const el = document.createElement('styled-box')
el.textContent = 'Hello'
document.body.appendChild(el)
console.log(getComputedStyle(el).borderStyle)
Question 14
Why prefer CSS custom properties for theming shadow DOM components?
Question 15
Which lifecycle hook runs when a custom element is moved to another document (e.g., via adoptNode)?
Question 16
What prints?
class ObservedEl extends HTMLElement {
static get observedAttributes() {
return ['value']
}
attributeChangedCallback(name, oldValue, newValue) {
console.log(name, oldValue, newValue)
}
}
customElements.define('observed-el', ObservedEl)
const el = document.createElement('observed-el')
el.setAttribute('value', 'A')
el.setAttribute('value', 'B')
Question 17
Why should reusable components expose attributes or properties for configuration?
Question 18
What prints?
class CounterDisplay extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' }).innerHTML = `<span></span>`
this.value = 0
}
set count(val) {
this.value = Number(val)
this.shadowRoot.querySelector('span').textContent = this.value
}
get count() {
return this.value
}
connectedCallback() {
this.count = this.getAttribute('count') ?? 0
}
}
customElements.define('counter-display', CounterDisplay)
const el = document.createElement('counter-display')
el.setAttribute('count', '5')
document.body.appendChild(el)
console.log(el.shadowRoot.textContent)
Question 19
Why move heavy DOM creation into templates instead of runtime string concatenation?
Question 20
What prints?
const tpl = document.createElement('template')
tpl.innerHTML = `<style>:host(.active){color:green;}</style><slot></slot>`
class StatusLabel extends HTMLElement {
constructor() {
super()
const root = this.attachShadow({ mode: 'open' })
root.appendChild(tpl.content.cloneNode(true))
}
}
customElements.define('status-label', StatusLabel)
const el = document.createElement('status-label')
el.classList.add('active')
el.textContent = 'Online'
document.body.appendChild(el)
console.log(getComputedStyle(el).color)
Question 21
How does event retargeting behave with shadow DOM?
Question 22
What prints?
class EventfulEl extends HTMLElement {
constructor() {
super()
const root = this.attachShadow({ mode: 'open' })
root.innerHTML = `<button>Shadow</button>`
root.querySelector('button').addEventListener('click', () => {
console.log('shadow handler')
this.dispatchEvent(new CustomEvent('shadow-click', { bubbles: true, composed: true }))
})
}
}
customElements.define('eventful-el', EventfulEl)
const el = document.createElement('eventful-el')
el.addEventListener('shadow-click', () => console.log('host heard'))
document.body.appendChild(el)
el.shadowRoot.querySelector('button').click()
Question 23
Why should shadow DOM components re-emit events rather than exposing internal nodes?
Question 24
What prints?
class SlotEl extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' }).innerHTML = `<slot name="prefix"></slot><slot></slot>`
}
}
customElements.define('slot-el', SlotEl)
const el = document.createElement('slot-el')
const span1 = document.createElement('span')
span1.slot = 'prefix'
span1.textContent = 'Hi '
const span2 = document.createElement('span')
span2.textContent = 'there'
el.append(span1, span2)
document.body.appendChild(el)
console.log(el.shadowRoot.textContent)
Question 25
Why should custom elements avoid global document.querySelector from inside shadow roots?
Question 26
What prints?
class ToggleButton extends HTMLElement {
constructor() {
super()
const root = this.attachShadow({ mode: 'open' })
root.innerHTML = `<button part="button">Off</button>`
this.button = root.querySelector('button')
this.button.addEventListener('click', () => {
const isOn = this.hasAttribute('on')
if (isOn) {
this.removeAttribute('on')
this.button.textContent = 'Off'
} else {
this.setAttribute('on', '')
this.button.textContent = 'On'
}
})
}
}
customElements.define('toggle-button', ToggleButton)
const el = document.createElement('toggle-button')
document.body.appendChild(el)
el.shadowRoot.querySelector('button').click()
console.log(el.hasAttribute('on'), el.shadowRoot.querySelector('button').textContent)
Question 27
Why expose parts via the part attribute?
Question 28
What prints?
class LightInput extends HTMLElement {
constructor() {
super()
const root = this.attachShadow({ mode: 'open' })
root.innerHTML = `<slot></slot><p id="value"></p>`
this.valueDisplay = root.querySelector('#value')
}
connectedCallback() {
this.addEventListener('input', (event) => {
this.valueDisplay.textContent = event.target.value
})
}
}
customElements.define('light-input', LightInput)
const el = document.createElement('light-input')
const input = document.createElement('input')
input.value = 'initial'
el.appendChild(input)
document.body.appendChild(el)
input.value = 'changed'
input.dispatchEvent(new Event('input', { bubbles: true }))
console.log(el.shadowRoot.querySelector('#value').textContent)
Question 29
Why avoid querySelector from outside to reach shadow DOM internals in production code?
Question 30
What prints?
class FallbackSlot extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' }).innerHTML = `<slot>Fallback</slot>`
}
}
customElements.define('fallback-slot', FallbackSlot)
const el = document.createElement('fallback-slot')
document.body.appendChild(el)
console.log(el.shadowRoot.textContent)
Question 31
Why use slotchange events?
Question 32
How do frameworks typically integrate with custom elements?
Question 33
What prints?
class ClosedLight extends HTMLElement {
constructor() {
super()
const root = this.attachShadow({ mode: 'closed' })
const btn = document.createElement('button')
btn.textContent = 'Hidden'
btn.addEventListener('click', () => console.log('shadow button clicked'))
root.append(btn)
}
}
customElements.define('closed-light', ClosedLight)
const el = document.createElement('closed-light')
document.body.appendChild(el)
try {
el.shadowRoot.querySelector('button').click()
} catch (error) {
console.log('access error')
}
Question 34
Why should keyboard accessibility be addressed in Web Components?
Question 35
Why document light/shadow interaction patterns in component docs?
