<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')
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.