JavaScript Shadow DOM & Web Components Basics Quiz

JavaScript
0 Passed
0% acceptance

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.

35 Questions
~70 minutes
1

Question 1

What is required before using a custom element tag in HTML?

A
Calling customElements.define() with a unique tag name that contains a hyphen
B
Importing React
C
Registering the tag in CSS
D
Declaring it inside <template>
2

Question 2

Why must custom element names contain a hyphen?

A
To avoid conflicts with existing and future HTML tags reserved by browsers
B
Because CSS requires it
C
To enable server rendering
D
It is optional according to the spec
3

Question 3

What prints?

javascript
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)
A
'Click me'
B
null
C
Throws because connectedCallback never fires
D
undefined
4

Question 4

Which lifecycle callback fires when an element is removed from the DOM?

A
disconnectedCallback
B
connectedCallback
C
attributeChangedCallback
D
adoptedCallback only
5

Question 5

Why extend HTMLElement instead of HTMLDivElement?

A
Autonomous custom elements must extend HTMLElement; built-in extensions use is="" syntax
B
divs lack styles
C
Extending HTMLElement removes lifecycle callbacks
D
Browsers ban other base classes
6

Question 6

What is a shadow root?

A
A document fragment attached to a host element that encapsulates DOM and styles
B
A hidden iframe
C
A CSS-only rule
D
A server-rendered component
7

Question 7

What prints?

javascript
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)
A
"" then "<span>Shadow!</span>"
B
"<span>Shadow!</span>" twice
C
Throws because attachShadow requires connectedCallback
D
undefined both times
8

Question 8

Why does shadow DOM provide style encapsulation by default?

A
Selectors inside the shadow root only affect nodes within that root, preventing leakage into light DOM
B
It converts styles to inline attributes
C
It require Sass preprocessing
D
It blocks all styles entirely
9

Question 9

What is the difference between open and closed shadow roots?

A
open exposes shadowRoot on the host for external JS access; closed keeps shadowRoot null
B
open loads styles, closed does not
C
closed roots are private to TypeScript only
D
There is no difference
10

Question 10

What prints?

javascript
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)
A
null
B
<p>secret</p>
C
DOMException
D
undefined but accessible via el._shadowRoot
11

Question 11

What prints?

javascript
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)
A
rgb(255, 0, 0)
B
rgb(0, 0, 0)
C
Throws because slot requires named attribute
D
undefined
12

Question 12

How do named slots work?

A
Elements with slot="name" in light DOM project into <slot name="name"> placeholders, enabling layout control
B
slot attributes rename CSS classes
C
Slots only support text nodes
D
Slots automatically style projected content
13

Question 13

What prints?

javascript
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)
A
solid
B
none
C
dashed
D
Throws because :host is unsupported
14

Question 14

Why prefer CSS custom properties for theming shadow DOM components?

A
Custom properties can cross the shadow boundary, letting the host supply theme values without breaking encapsulation
B
Shadow DOM ignores CSS variables
C
They eliminate the need for attributes
D
They disable inheritance
15

Question 15

Which lifecycle hook runs when a custom element is moved to another document (e.g., via adoptNode)?

A
adoptedCallback
B
connectedCallback
C
disconnectedCallback only
D
attributeChangedCallback
16

Question 16

What prints?

javascript
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')
A
"value" null "A" then "value" "A" "B"
B
No logs until connected
C
Only "value" null "B"
D
Throws because observedAttributes must be getterless
17

Question 17

Why should reusable components expose attributes or properties for configuration?

A
Consumers need declarative ways to pass data without reaching into shadow DOM internals
B
Attributes automatically generate CSS
C
Properties cannot be observed
D
Shadow DOM forbids data input
18

Question 18

What prints?

javascript
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)
A
5
B
0
C
D
Throws because setters cannot access shadowRoot
19

Question 19

Why move heavy DOM creation into templates instead of runtime string concatenation?

A
Templates can be cloned efficiently and keep markup in one place, improving readability and performance
B
Templates auto-register custom elements
C
String concatenation is forbidden in shadow DOM
D
Templates require frameworks
20

Question 20

What prints?

javascript
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)
A
rgb(0, 128, 0)
B
rgb(0, 0, 0)
C
Throws because :host cannot use classes
D
rgb(255, 0, 0)
21

Question 21

How does event retargeting behave with shadow DOM?

A
Events dispatched inside shadow DOM appear to originate from the host when observed outside, preserving encapsulation
B
Events are blocked entirely
C
Events always leak original targets
D
Event bubbling stops at the shadow root
22

Question 22

What prints?

javascript
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()
A
shadow handler \n host heard
B
host heard only
C
shadow handler only
D
No logs; event can’t bubble
23

Question 23

Why should shadow DOM components re-emit events rather than exposing internal nodes?

A
It maintains encapsulation while still letting consumers react to user interaction
B
Events mutate CSS automatically
C
Exposing nodes disables slots
D
Shadow DOM forbids CustomEvent
24

Question 24

What prints?

javascript
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)
A
Hi there
B
there Hi
C
D
slot prefix
25

Question 25

Why should custom elements avoid global document.querySelector from inside shadow roots?

A
Accessing document directly breaks encapsulation and can slow components; prefer this.shadowRoot.querySelector
B
document.querySelector is deprecated
C
Shadow DOM disables document API
D
There is no other way to select nodes
26

Question 26

What prints?

javascript
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)
A
true "On"
B
false "Off"
C
false "On"
D
Throws because attributes cannot be toggled inside shadow DOM
27

Question 27

Why expose parts via the part attribute?

A
part allows the ::part() selector from outside, enabling styling hooks without losing encapsulation
B
part copies innerHTML to the host
C
part is required for slots
D
part disables CSS custom properties
28

Question 28

What prints?

javascript
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)
A
changed
B
initial
C
D
Throws because events cannot cross slot boundaries
29

Question 29

Why avoid querySelector from outside to reach shadow DOM internals in production code?

A
Closed roots make it impossible; even open roots can change structure, breaking consumers
B
querySelector is slow
C
Shadow DOM forbids read access
D
This is the recommended integration point
30

Question 30

What prints?

javascript
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)
A
Fallback
B
C
undefined
D
Throws because slots need assigned nodes
31

Question 31

Why use slotchange events?

A
They notify components when assigned light DOM nodes change, allowing dynamic responses
B
They update CSS automatically
C
They trigger lifecycle callbacks
D
Slots change only during initial render
32

Question 32

How do frameworks typically integrate with custom elements?

A
They treat custom elements as standard DOM nodes, passing props via attributes/properties and listening for CustomEvents
B
They require special compilation
C
They cannot render custom elements
D
They automatically open shadow roots
33

Question 33

What prints?

javascript
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')
}
A
access error
B
shadow button clicked
C
Hidden
D
Throws at define time
34

Question 34

Why should keyboard accessibility be addressed in Web Components?

A
Shadow DOM does not automatically provide focus management; component authors must wire ARIA roles, tabindex, and re-emit events for accessibility
B
Browsers block keyboard events in shadow DOM
C
Custom elements cannot host form controls
D
Accessibility is provided by slots automatically
35

Question 35

Why document light/shadow interaction patterns in component docs?

A
Consumers need to know what content slots exist, which attributes map to behavior, and how events bubble out of the shadow DOM
B
Documentation forces open shadow roots
C
It replaces TypeScript
D
It removes the need for tests

QUIZZES IN JavaScript