<h1>Adam's list of Dynamic Styling Patterns</h1>

<div class="buttons">
  <span>Set Color:</span>
  
  <input type="radio" name="color" id="red" value="red" >
  <label for="red">Red</label>
  
  <input type="radio" name="color" id="green" value="green">
  <label for="green">Green</label>
  
  <input type="radio" name="color" id="teal" value="teal" >
  <label for="teal">Teal</label>
  
  <input type="radio" name="color" id="blue" value="blue">
  <label for="blue">Blue</label>
  
  <input type="radio" name="color" id="purple" value="purple" checked>
  <label for="purple">Purple</label>
</div>

<div class="pill-examples">
  
  <div class="example">
    
    <h1>Class-switched property assignment</h1>
    
    <p>Use JS to set a class. Directly update style declarations based on the class</p>
    
    <span class="pill" id="option-1">Pattern 1</span>
    
    <div class="pros"><b>Pros</b>: Simple</div>
    <div class="cons"><b>Cons</b>: All style declarations are rewritten (not DRY), Need to maintain the class list of possible values. Duplicated declarations (slightly) slow CSSOM construction.</div>
      
  </div>
  
  <div class="example">
    <h1>Class-switched Mixin call</h1>
    
    <p>Use JS to set a class. Call a mixin with the appropriate value based on the class.</p>
    
    <span class="pill" id="option-2">Pattern 2</span>
    
    <div class="pros"><b>Pros</b>: DRY - Style declarations are written once. Logic is done in pre-processor land</div>
    <div class="cons"><b>Cons</b>: Need to maintain the class list of possible values. Duplicated declarations are (slightly) slower</div>
    
  </div>

  
  
  <div class="example">
    <h1>Class-switched custom properties</h1>
    
    <p>Use JS to set a class. Set a custom property based on the class</p>

    <span class="pill" id="option-3">Pattern 3</span>
    
    <div class="pros"><b>Pros</b>: DRY - Style declarations are written once. Pure CSS (no pre-processor features)</div>
    <div class="cons"><b>Cons</b>: Need to maintain the class list of possible values</div>
    
  </div>

  
  <div class="example">
    <h1>JS switched custom properties</h1>
    
    <p>Use JS to set the custom property reference. Adam's favourite 🌟</p>

    <span class="pill" id="option-4">Pattern 4</span>
    
    <div class="pros"><b>Pros</b>: DRY - Style declarations are written once. Clean & minimal CSS and JS code</div>
    <div class="cons"><b>Cons</b>: Need to know the name of the CSS variables used</div>
    
  </div>
  
  
  <div class="example">
    <h1>JS defined custom property</h1>
    
    <p>Use JS to assign a value to the custom property</p>
    
    <span class="pill" id="option-5">Pattern 5</span>
    
    <div class="pros"><b>Pros</b>: Keeps logic in JS, DRY - Style declarations are written once.</div>
    <div class="cons"><b>Cons</b>: Need to store key/value map in JS</div>
  </div>
  
  <div class="example">
    <h1>Inline style manipulation</h1>
    
    <p>Use JS to set the style attribute directly</p>
    
    <span class="pill" id="option-6">Pattern 6</span>
    
    <div class="pros"><b>Pros</b>: Everything lives in JS, Technically faster to render.</div>
    <div class="cons"><b>Cons</b>: Potential specificity conflicts, Need to store key/value map in JS, Need to update each property — not DRY</div>
    
  </div>
  
  <div class="example">
    <!-- Thanks to Adam Argyle  - @argyleink for highlighting this approach -->
    <h1>CSSOM manipulation</h1>
    
    <p>Similar to inline styling, but here we use JS to <a href="https://developers.google.com/web/updates/2018/03/cssom">update the CSSOM directly</a>.</p>

    <span class="pill" id="option-7">Pattern 7</span>

    <div class="pros"><b>Pros</b>: Faster than inline, good unit type support</div>
    <div class="cons"><b>Cons</b>: Potential specificity conflicts, Need to store key/value map in JS, Need to update each property — not DRY. Limited browser support (Chrome & Edge)</div>
  </div>
  
  <div class="example">
    <!-- Thanks to Adam Argyle  - @argyleink for highlighting this approach -->
    <h1>Attribute switched custom properties</h1>
    
    <p>Similar to class-switching. Use JS to set an HTML attribute. Set a custom property based on that attribute</p>

    <span class="pill" id="option-8">Pattern 8</span>

    <div class="pros"><b>Pros</b>: Attributes are easier to update than classList, DRY - Style declarations are written once.</div>
    <div class="cons"><b>Cons</b>: Need to maintain all possible values in CSS</div>
  </div>  
  
</div>
// Define base colors
:root {
  --color-red: #c20018;
  --color-green: #009b48;
  --color-teal: #0ac4ac;
  --color-blue: #0a4ace;
  --color-purple: #662d91;
}

// Option 1 — class-switched assignment
.pill#option-1 {
  outline-style: solid;
  outline-width: 1px;
  
  &.red { 
    background-color: var(--color-red);
    // ... other styles that rely on --color-*
  }
  &.green { 
    background-color: var(--color-green);
    // ... other styles that rely on --color-*
  }
  &.blue { 
    background-color: var(--color-blue);
    // ... other styles that rely on --color-*
  }
  &.teal { 
    background-color: var(--color-teal);
    // ... other styles that rely on --color-*
  }
  &.purple { 
    background-color: var(--color-purple);
    // ... other styles that rely on --color-*
  }
}

// Option 2 — class-switched Mixin
.pill#option-2 {
  @mixin pill-color($color) {
    background-color: var(--color-#{$color});
    // ... other styles that rely on --pill-color
  }
  
  &.red { @include pill-color('red')}
  &.green { @include pill-color('green')}
  &.blue { @include pill-color('blue')}
  &.teal { @include pill-color('teal')}
  &.purple { @include pill-color('purple')}
}

// Option 3 - class-switched custom properties
.pill#option-3 {
  
  &.red { --pill-color: var(--color-red) }
  &.green { --pill-color: var(--color-green) }
  &.blue { --pill-color: var(--color-blue) }
  &.teal { --pill-color: var(--color-teal) }
  &.purple { --pill-color: var(--color-purple) }
  
  background-color: var(--pill-color)
  // ... other styles that rely on --pill-color
}

// Option 4 - JS switched custom properties
.pill#option-4 {
  background-color: var(--pill-color)
  // ... other styles that rely on --pill-color
}

// Option 5 - JS defined custom property
.pill#option-5 {
  background-color: var(--pill-color)
  // ... other styles that rely on --pill-color
}

// Option 6 - Use inline styling
.pill#option-6 {}

// Option 7 - CSSOM manipulation
// https://developers.google.com/web/updates/2018/03/cssom
.pill#option-7 {}

// Option 8 - Attribute switching
.pill#option-8 {
  &[color="red"] { --pill-color: var(--color-red) }
  &[color="green"] { --pill-color: var(--color-green) }
  &[color="blue"] { --pill-color: var(--color-blue) }
  &[color="teal"] { --pill-color: var(--color-teal) }
  &[color="purple"] { --pill-color: var(--color-purple) }
  
  background-color: var(--pill-color)
  // ... other styles that rely on --pill-color
}

.pill {
  position: relative;
  font-size: 16px;
  padding: 0.25em 1em;
  border-radius: 1em;
  background-color: black;
  color: white;
  cursor: pointer;
  align-self: flex-start;
  justify-self: center;
  grid-row: 1 / 2;
  margin: 8px auto;
}

.pill-examples {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(32ch, 1fr));
  gap: 16px;
  width: 100%;
  
  .example {
    display: grid;
    gap: 8px;
    grid-auto-rows: max-content;
    padding: 16px;
    border: 1px solid #ccc;
    border-radius: 8px;
    background-color: white;
  }
  
  h1 {
    font-size: 20px;
    margin: 0;
    color: #161616;
  }
  
  p {
    margin: 0;
    color: #333;
  }
  
  .pros, .cons {
    font-size: 14px;
    color: #808080;
  }
}

body {
  font-family: Roboto, --apple-system, sans-serif;
  display: flex;
  width: 100vw;
  max-width: 1440px;
  flex-direction: column;
  gap: 16px;
  box-sizing: border-box;
  margin: 0 auto;
  padding: 24px;
  align-items: center;
  justify-content: center;
  background-color: #fafafe;
  color: #333;
}
View Compiled
setPillColors('purple')

function setPillColors(color) {
  
  // Option 1 - JS updated Class
  const option1 = document.querySelector('#option-1')
  clearClasses(option1)
  option1.classList.add(color)
  
  // Option 2 - JS updated Class w/ mixins
  const option2 = document.querySelector('#option-2')
  clearClasses(option2)
  option2.classList.add(color)

  // Option 3 - JS updated class w/ custom props
  const option3 = document.querySelector('#option-3')
  clearClasses(option3)
  option3.classList.add(color)

  // Option 4 - JS switched custom properties
  const option4 = document.querySelector('#option-4')
  option4.style.setProperty('--pill-color', `var(--color-${color})`)

  // Option 5 - JS assigned custom property
  const option5 = document.querySelector('#option-5')
  const COLOR_DEFS = {
    red: "#c20018",
    green: "#009b48",
    teal: "#0ac4ac",
    blue: "#0a4ace",
    purple: "#662d91",
  }
  option5.style.setProperty('--pill-color', COLOR_DEFS[color]) 
  
  // Option 6 - Direct styling
  const option6 = document.querySelector('#option-6')
  option6.style.backgroundColor = COLOR_DEFS[color]
  
  // Option 7 - attributeStyleMap
  const option7 = document.querySelector('#option-7')
  if(window.CSS && CSS.number) {
    option7.attributeStyleMap.set('background-color', COLOR_DEFS[color])
  }
  
  // Option 8 - attribute toggling
  const option8 = document.querySelector('#option-8');
  option8.setAttribute('color', color)
  
}

// Add listeners to the radio buttons
const radios = document.querySelectorAll('input[type="radio"][name="color"]')
radios.forEach(radio => radio.addEventListener('change', setColor))
function setColor({target : {value}}) {
  setPillColors(value)
}

// Remove all color classes from an element
// Not needed if using a framework like Vue, React, Angular etc.
function clearClasses(element) {
  element.classList.remove('red')
  element.classList.remove('green')
  element.classList.remove('teal')
  element.classList.remove('blue')
  element.classList.remove('purple')
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.