CSS and Styling Polyfills
1. CSS Custom Properties (Variables) Polyfills
| Feature | Syntax | Browser Support | Polyfill Method |
|---|---|---|---|
| Define Variable | :root { --main-color: #06c; } |
Modern browsers | Parse CSS, replace with computed values |
| Use Variable | color: var(--main-color); |
IE not supported | Runtime substitution |
| Fallback Value | var(--color, blue) |
Second parameter | Parse fallback in polyfill |
| JavaScript Access | getComputedStyle().getPropertyValue('--var') |
Modern only | Custom property tracking |
| Dynamic Update | element.style.setProperty('--var', value) |
Modern only | Watch for changes, re-apply |
Example: CSS Custom Properties polyfill
// CSS Variables polyfill for IE11
(function() {
if (window.CSS && CSS.supports && CSS.supports('color', 'var(--test)')) {
return; // Native support
}
var CSSVariables = {
variables: {},
init: function() {
this.parseStyleSheets();
this.applyVariables();
this.watchForChanges();
},
parseStyleSheets: function() {
var sheets = document.styleSheets;
for (var i = 0; i < sheets.length; i++) {
try {
var rules = sheets[i].cssRules || sheets[i].rules;
if (!rules) continue;
for (var j = 0; j < rules.length; j++) {
this.parseRule(rules[j]);
}
} catch(e) {
// Cross-origin stylesheets may throw
console.warn('Cannot access stylesheet:', e);
}
}
},
parseRule: function(rule) {
// Parse variable definitions from :root or custom selectors
if (rule.selectorText &&
(rule.selectorText.indexOf(':root') >= 0 ||
rule.selectorText.indexOf('html') >= 0)) {
var style = rule.style;
for (var i = 0; i < style.length; i++) {
var prop = style[i];
if (prop.indexOf('--') === 0) {
this.variables[prop] = style.getPropertyValue(prop).trim();
}
}
}
// Parse @media rules recursively
if (rule.cssRules) {
for (var i = 0; i < rule.cssRules.length; i++) {
this.parseRule(rule.cssRules[i]);
}
}
},
applyVariables: function() {
var sheets = document.styleSheets;
for (var i = 0; i < sheets.length; i++) {
try {
var rules = sheets[i].cssRules || sheets[i].rules;
if (!rules) continue;
for (var j = 0; j < rules.length; j++) {
this.processRule(rules[j]);
}
} catch(e) {}
}
// Also process inline styles
var elements = document.querySelectorAll('[style*="var("]');
for (var i = 0; i < elements.length; i++) {
this.processInlineStyle(elements[i]);
}
},
processRule: function(rule) {
if (!rule.style) return;
var style = rule.style;
var changes = {};
for (var i = 0; i < style.length; i++) {
var prop = style[i];
var value = style.getPropertyValue(prop);
if (value.indexOf('var(') >= 0) {
var resolved = this.resolveValue(value);
changes[prop] = resolved;
}
}
// Apply resolved values
for (var prop in changes) {
style.setProperty(prop, changes[prop], style.getPropertyPriority(prop));
}
// Process nested rules
if (rule.cssRules) {
for (var i = 0; i < rule.cssRules.length; i++) {
this.processRule(rule.cssRules[i]);
}
}
},
processInlineStyle: function(element) {
var style = element.getAttribute('style');
if (!style) return;
var resolved = this.resolveValue(style);
if (resolved !== style) {
element.setAttribute('style', resolved);
}
},
resolveValue: function(value) {
var self = this;
// Match var(--variable-name) or var(--variable-name, fallback)
return value.replace(/var\(\s*(--[^,\)]+)(?:\s*,\s*([^\)]+))?\s*\)/g,
function(match, varName, fallback) {
var resolved = self.variables[varName.trim()];
if (resolved !== undefined) {
return resolved;
} else if (fallback) {
return fallback.trim();
}
return match; // Keep original if not found
});
},
watchForChanges: function() {
// Watch for new style elements
if (window.MutationObserver) {
var observer = new MutationObserver(function(mutations) {
var needsUpdate = false;
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length) {
needsUpdate = true;
}
});
if (needsUpdate) {
setTimeout(function() {
this.parseStyleSheets();
this.applyVariables();
}.bind(this), 100);
}
}.bind(this));
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
}
},
// Public API for JavaScript access
getVariable: function(name) {
return this.variables[name];
},
setVariable: function(name, value) {
this.variables[name] = value;
this.applyVariables();
}
};
// Initialize on DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
CSSVariables.init();
});
} else {
CSSVariables.init();
}
// Expose API
window.CSSVariablesPolyfill = CSSVariables;
})();
Note: CSS variable polyfills like
css-vars-ponyfill provide more complete
implementations with better performance. This example shows the basic concept.
2. CSS Grid and Flexbox Layout Polyfills
| Feature | Support | Polyfill Strategy | Limitations |
|---|---|---|---|
| Flexbox | IE10+ (prefixed) | Vendor prefixes, flexibility polyfill | IE9 needs float fallback |
| CSS Grid | IE10+ (old spec) | Autoprefixer, PostCSS | IE11 has limited grid support |
| Grid Template Areas | Modern browsers | Not polyfillable in IE | Use line-based positioning |
| gap Property | Modern browsers | Margin fallback | Different box model behavior |
| auto-fit/auto-fill | Modern browsers | Not polyfillable | Use fixed column count |
Example: Flexbox vendor prefix polyfill
// Flexbox vendor prefix helper
var Flexbox = {
prefixes: ['webkit', 'moz', 'ms', 'o', ''],
properties: {
'display': {
'flex': ['-webkit-box', '-moz-box', '-ms-flexbox', '-webkit-flex', 'flex'],
'inline-flex': ['-webkit-inline-box', '-moz-inline-box', '-ms-inline-flexbox',
'-webkit-inline-flex', 'inline-flex']
},
'flex-direction': {
prefixed: true,
oldBoxProp: 'box-orient',
values: {
'row': 'horizontal',
'column': 'vertical'
}
},
'flex': { prefixed: true, oldBoxProp: 'box-flex' },
'justify-content': { prefixed: true, oldBoxProp: 'box-pack' },
'align-items': { prefixed: true, oldBoxProp: 'box-align' }
},
applyPrefixes: function(element, property, value) {
if (property === 'display' && (value === 'flex' || value === 'inline-flex')) {
// Apply all display prefixes
var values = this.properties.display[value];
values.forEach(function(val) {
element.style.display = val;
});
} else if (this.properties[property] && this.properties[property].prefixed) {
// Apply prefixed property
this.prefixes.forEach(function(prefix) {
var prop = prefix ? '-' + prefix + '-' + property : property;
element.style.setProperty(prop, value);
});
}
},
enableFlexbox: function(selector) {
var elements = document.querySelectorAll(selector);
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
var computed = window.getComputedStyle(element);
// Check if flex display is used
if (computed.display === 'flex' || computed.display === 'inline-flex') {
this.applyPrefixes(element, 'display', computed.display);
}
}
}
};
// Auto-apply on load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
Flexbox.enableFlexbox('[style*="flex"]');
});
} else {
Flexbox.enableFlexbox('[style*="flex"]');
}
Example: Grid gap polyfill with margins
// CSS Grid gap polyfill for IE11
(function() {
// Check if gap is supported
if (CSS.supports('gap', '1px')) {
return;
}
var GridGapPolyfill = {
init: function() {
var grids = document.querySelectorAll('[style*="grid"], .grid, [class*="grid"]');
for (var i = 0; i < grids.length; i++) {
this.processGrid(grids[i]);
}
},
processGrid: function(grid) {
var computed = window.getComputedStyle(grid);
// Check for grid display
if (computed.display.indexOf('grid') === -1) {
return;
}
// Get gap values
var gap = this.getGapValue(grid);
if (gap.row || gap.column) {
this.applyGapFallback(grid, gap);
}
},
getGapValue: function(element) {
var style = element.getAttribute('style') || '';
var gap = { row: 0, column: 0 };
// Parse gap, grid-gap, row-gap, column-gap
var gapMatch = style.match(/(?:grid-)?gap:\s*([^;]+)/);
if (gapMatch) {
var values = gapMatch[1].trim().split(/\s+/);
gap.row = this.parseValue(values[0]);
gap.column = values[1] ? this.parseValue(values[1]) : gap.row;
}
var rowGapMatch = style.match(/(?:grid-)?row-gap:\s*([^;]+)/);
if (rowGapMatch) {
gap.row = this.parseValue(rowGapMatch[1]);
}
var colGapMatch = style.match(/(?:grid-)?column-gap:\s*([^;]+)/);
if (colGapMatch) {
gap.column = this.parseValue(colGapMatch[1]);
}
return gap;
},
parseValue: function(value) {
var num = parseFloat(value);
return isNaN(num) ? 0 : num;
},
applyGapFallback: function(grid, gap) {
var children = grid.children;
for (var i = 0; i < children.length; i++) {
var child = children[i];
// Apply margin fallback
if (gap.row) {
child.style.marginBottom = gap.row + 'px';
}
if (gap.column) {
child.style.marginRight = gap.column + 'px';
}
}
// Adjust grid container to compensate
if (gap.row) {
grid.style.marginBottom = '-' + gap.row + 'px';
}
if (gap.column) {
grid.style.marginRight = '-' + gap.column + 'px';
}
}
};
// Initialize
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
GridGapPolyfill.init();
});
} else {
GridGapPolyfill.init();
}
})();
Warning: CSS Grid polyfills for IE11 have significant
limitations. Consider using
Autoprefixer with PostCSS for build-time transformations or
provide float-based fallbacks.
3. CSS calc() Function Polyfills
| Feature | Syntax | Browser Support | Polyfill Approach |
|---|---|---|---|
| Basic calc() | width: calc(100% - 20px) |
IE9+ | Parse and compute at runtime |
| Nested calc() | calc(100% - calc(10px * 2)) |
Modern browsers | Recursive evaluation |
| Mixed Units | calc(100vh - 50px) |
Requires viewport units | Convert to pixels at runtime |
| Vendor Prefixes | -webkit-calc(), -moz-calc() |
Old browsers | Add all prefixes |
Example: CSS calc() polyfill
// CSS calc() polyfill for older browsers
var CalcPolyfill = {
init: function() {
// Check for calc support
if (this.supportsCalc()) {
return;
}
this.processStyleSheets();
this.processInlineStyles();
},
supportsCalc: function() {
var div = document.createElement('div');
div.style.width = 'calc(10px)';
return div.style.width !== '';
},
processStyleSheets: function() {
var sheets = document.styleSheets;
for (var i = 0; i < sheets.length; i++) {
try {
var rules = sheets[i].cssRules || sheets[i].rules;
if (!rules) continue;
for (var j = 0; j < rules.length; j++) {
this.processRule(rules[j]);
}
} catch(e) {}
}
},
processRule: function(rule) {
if (!rule.style) return;
var style = rule.style;
for (var i = 0; i < style.length; i++) {
var prop = style[i];
var value = style.getPropertyValue(prop);
if (value.indexOf('calc(') >= 0) {
var computed = this.evaluateCalc(value, null);
if (computed) {
style.setProperty(prop, computed);
}
}
}
},
processInlineStyles: function() {
var elements = document.querySelectorAll('[style*="calc("]');
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
var style = element.getAttribute('style');
if (style) {
var processed = this.processStyleString(style, element);
element.setAttribute('style', processed);
}
}
},
processStyleString: function(style, element) {
var self = this;
return style.replace(/([^:]+):\s*([^;]*calc\([^;]+)(?:;|$)/g,
function(match, prop, value) {
var computed = self.evaluateCalc(value, element);
return computed ? prop + ': ' + computed + ';' : match;
});
},
evaluateCalc: function(value, element) {
var self = this;
// Extract calc() expressions
return value.replace(/calc\(([^\)]+)\)/g, function(match, expression) {
try {
// Convert percentage and viewport units if element provided
if (element) {
expression = self.convertUnits(expression, element);
}
// Evaluate the mathematical expression
var result = self.evalMath(expression);
return result + 'px';
} catch(e) {
console.warn('Failed to evaluate calc:', expression, e);
return match;
}
});
},
convertUnits: function(expression, element) {
var parent = element.parentElement;
// Convert % to px based on parent
if (expression.indexOf('%') >= 0 && parent) {
var parentWidth = parent.offsetWidth;
expression = expression.replace(/(\d+(?:\.\d+)?)%/g, function(match, num) {
return (parseFloat(num) * parentWidth / 100) + '';
});
}
// Convert viewport units
expression = expression.replace(/(\d+(?:\.\d+)?)vw/g, function(match, num) {
return (parseFloat(num) * window.innerWidth / 100) + '';
});
expression = expression.replace(/(\d+(?:\.\d+)?)vh/g, function(match, num) {
return (parseFloat(num) * window.innerHeight / 100) + '';
});
// Remove px unit for calculation
expression = expression.replace(/(\d+(?:\.\d+)?)px/g, '$1');
return expression;
},
evalMath: function(expression) {
// Simple mathematical expression evaluator
expression = expression.replace(/\s+/g, '');
// Basic validation - only allow numbers, operators, parentheses, and dots
if (!/^[\d\+\-\*\/\(\)\.\s]+$/.test(expression)) {
throw new Error('Invalid expression');
}
// Evaluate using Function constructor (safer than eval)
return new Function('return ' + expression)();
}
};
// Initialize on load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
CalcPolyfill.init();
});
} else {
CalcPolyfill.init();
}
Warning: Runtime calc() polyfills don't handle responsive
changes automatically. Consider using CSS preprocessors (SASS/LESS) for build-time calculations
instead.
4. Media Queries and Responsive Design Polyfills
| Feature | Browser Support | Polyfill | Notes |
|---|---|---|---|
| @media queries | IE9+ | Respond.js for IE8 | Min/max-width only |
| matchMedia() | IE10+ | matchMedia.js polyfill | JavaScript query API |
| @supports | Modern browsers | CSS.supports() polyfill | Feature detection |
| prefers-color-scheme | Modern browsers | JavaScript detection | Dark mode support |
| Viewport meta | Mobile browsers | Not needed for desktop | <meta name="viewport"> |
Example: matchMedia polyfill
// window.matchMedia polyfill for IE9
if (!window.matchMedia) {
window.matchMedia = (function() {
'use strict';
var styleMedia = (window.styleMedia || window.media);
if (!styleMedia) {
var style = document.createElement('style');
var script = document.getElementsByTagName('script')[0];
var info = null;
style.type = 'text/css';
style.id = 'matchmediajs-test';
script.parentNode.insertBefore(style, script);
info = ('getComputedStyle' in window) &&
window.getComputedStyle(style, null) ||
style.currentStyle;
styleMedia = {
matchMedium: function(media) {
var text = '@media ' + media +
'{ #matchmediajs-test { width: 1px; } }';
if (style.styleSheet) {
style.styleSheet.cssText = text;
} else {
style.textContent = text;
}
return info.width === '1px';
}
};
}
return function(media) {
return {
matches: styleMedia.matchMedium(media || 'all'),
media: media || 'all'
};
};
})();
}
// Usage
var mql = window.matchMedia('(min-width: 768px)');
if (mql.matches) {
console.log('Desktop view');
} else {
console.log('Mobile view');
}
// Add listener for changes (modern API)
if (mql.addEventListener) {
mql.addEventListener('change', function(e) {
console.log('Media query changed:', e.matches);
});
} else {
// Legacy API
mql.addListener(function(mql) {
console.log('Media query changed:', mql.matches);
});
}
Example: Prefers-color-scheme detection
// Dark mode / prefers-color-scheme polyfill
var ColorScheme = {
query: '(prefers-color-scheme: dark)',
getPreference: function() {
// Try matchMedia first
if (window.matchMedia && window.matchMedia(this.query).matches) {
return 'dark';
}
// Fallback: check localStorage
var saved = localStorage.getItem('color-scheme');
if (saved) {
return saved;
}
// Fallback: check time of day
var hour = new Date().getHours();
if (hour >= 20 || hour <= 6) {
return 'dark';
}
return 'light';
},
setPreference: function(scheme) {
localStorage.setItem('color-scheme', scheme);
this.apply(scheme);
},
apply: function(scheme) {
document.documentElement.setAttribute('data-color-scheme', scheme);
// For older browsers, add class
if (scheme === 'dark') {
document.documentElement.classList.add('dark-mode');
} else {
document.documentElement.classList.remove('dark-mode');
}
},
watch: function() {
var self = this;
if (window.matchMedia) {
var mql = window.matchMedia(this.query);
var handler = function(e) {
var scheme = e.matches ? 'dark' : 'light';
self.apply(scheme);
};
// Modern API
if (mql.addEventListener) {
mql.addEventListener('change', handler);
} else {
// Legacy API
mql.addListener(handler);
}
}
},
init: function() {
var preference = this.getPreference();
this.apply(preference);
this.watch();
}
};
// Initialize
ColorScheme.init();
// CSS usage:
// [data-color-scheme="dark"] { background: #000; color: #fff; }
// .dark-mode { background: #000; color: #fff; }
Note: For IE8 support, use
Respond.js. For modern media queries like
prefers-reduced-motion, provide JavaScript-based alternatives.
5. CSS4 Selector Polyfills (:has, :is, :where)
| Selector | Description | Support | Polyfill Method |
|---|---|---|---|
| :has() | Parent selector | NEW | JavaScript class toggling |
| :is() | Matches-any pseudo-class | Modern browsers | Expand to multiple selectors |
| :where() | Zero-specificity :is() | Modern browsers | Expand to multiple selectors |
| :not() complex | Multiple selectors in :not() | Modern browsers | Expand to multiple :not() |
| :focus-visible | Keyboard focus indicator | Modern browsers | focus-visible.js polyfill |
Example: :has() selector polyfill
// :has() selector polyfill
var HasSelectorPolyfill = {
supported: null,
isSupported: function() {
if (this.supported !== null) {
return this.supported;
}
try {
document.querySelector(':has(*)');
this.supported = true;
} catch(e) {
this.supported = false;
}
return this.supported;
},
init: function() {
if (this.isSupported()) {
return;
}
// Add class to indicate polyfill is active
document.documentElement.classList.add('no-has-selector');
// Process common :has() patterns
this.processPattern('[data-has]');
},
processPattern: function(attribute) {
var elements = document.querySelectorAll(attribute);
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
var selector = element.getAttribute(attribute);
this.applyHasLogic(element, selector);
}
},
applyHasLogic: function(element, selector) {
// Check if element has matching children
var hasMatch = element.querySelector(selector) !== null;
if (hasMatch) {
element.classList.add('has-' + this.sanitizeSelector(selector));
} else {
element.classList.remove('has-' + this.sanitizeSelector(selector));
}
// Watch for changes
this.watchElement(element, selector);
},
sanitizeSelector: function(selector) {
return selector.replace(/[^a-z0-9-]/gi, '-');
},
watchElement: function(element, selector) {
if (!window.MutationObserver) {
return;
}
var self = this;
var observer = new MutationObserver(function() {
self.applyHasLogic(element, selector);
});
observer.observe(element, {
childList: true,
subtree: true
});
}
};
// Initialize
HasSelectorPolyfill.init();
// Usage in HTML:
// <div data-has=".error">...</div>
//
// CSS:
// div:has(.error) { border-color: red; }
// /* Fallback for polyfill: */
// .no-has-selector div.has-error { border-color: red; }
Example: :focus-visible polyfill
// :focus-visible polyfill
(function() {
var hadKeyboardEvent = true;
var hadFocusVisibleRecently = false;
var hadFocusVisibleRecentlyTimeout = null;
var inputTypesWhitelist = {
text: true,
search: true,
url: true,
tel: true,
email: true,
password: true,
number: true,
date: true,
month: true,
week: true,
time: true,
datetime: true,
'datetime-local': true
};
function focusTriggersKeyboardModality(el) {
var type = el.type;
var tagName = el.tagName;
if (tagName === 'INPUT' && inputTypesWhitelist[type] && !el.readOnly) {
return true;
}
if (tagName === 'TEXTAREA' && !el.readOnly) {
return true;
}
if (el.isContentEditable) {
return true;
}
return false;
}
function addFocusVisibleClass(el) {
if (el.classList.contains('focus-visible')) {
return;
}
el.classList.add('focus-visible');
el.setAttribute('data-focus-visible-added', '');
}
function removeFocusVisibleClass(el) {
if (!el.hasAttribute('data-focus-visible-added')) {
return;
}
el.classList.remove('focus-visible');
el.removeAttribute('data-focus-visible-added');
}
function onKeyDown(e) {
if (e.metaKey || e.altKey || e.ctrlKey) {
return;
}
hadKeyboardEvent = true;
}
function onPointerDown() {
hadKeyboardEvent = false;
}
function onFocus(e) {
if (focusTriggersKeyboardModality(e.target) || hadKeyboardEvent) {
addFocusVisibleClass(e.target);
}
}
function onBlur(e) {
if (e.target.classList.contains('focus-visible')) {
hadFocusVisibleRecently = true;
clearTimeout(hadFocusVisibleRecentlyTimeout);
hadFocusVisibleRecentlyTimeout = setTimeout(function() {
hadFocusVisibleRecently = false;
}, 100);
removeFocusVisibleClass(e.target);
}
}
// Check if already supported
try {
document.querySelector(':focus-visible');
return;
} catch(e) {
// Not supported, apply polyfill
}
// Add class to root
document.documentElement.classList.add('js-focus-visible');
// Add event listeners
document.addEventListener('keydown', onKeyDown, true);
document.addEventListener('mousedown', onPointerDown, true);
document.addEventListener('pointerdown', onPointerDown, true);
document.addEventListener('touchstart', onPointerDown, true);
document.addEventListener('focus', onFocus, true);
document.addEventListener('blur', onBlur, true);
})();
// CSS usage:
// .js-focus-visible :focus:not(.focus-visible) { outline: none; }
// :focus-visible { outline: 2px solid blue; }
Warning: CSS4 selectors like :has() cannot be fully polyfilled
with perfect performance. Consider using data attributes and JavaScript for complex parent selectors.
6. CSS Container Queries Polyfill Implementation
| Feature | Status | Polyfill Approach | Limitations |
|---|---|---|---|
| @container | EXPERIMENTAL | ResizeObserver + class toggling | Performance overhead |
| container-type | Modern browsers | Data attribute fallback | Requires polyfill awareness |
| container-name | Modern browsers | Custom data attributes | Manual setup |
| Size queries | width, height, inline-size, block-size | Measure container dimensions | Reflow performance |
| cqi/cqw/cqh units | Container query units | Calculate relative units | Complex calculations |
Example: Container queries polyfill
// Container Queries polyfill
var ContainerQueriesPolyfill = {
supported: null,
containers: [],
isSupported: function() {
if (this.supported !== null) {
return this.supported;
}
this.supported = CSS.supports('container-type', 'inline-size');
return this.supported;
},
init: function() {
if (this.isSupported()) {
return;
}
// Find all containers
var elements = document.querySelectorAll('[data-container]');
for (var i = 0; i < elements.length; i++) {
this.registerContainer(elements[i]);
}
},
registerContainer: function(element) {
var queries = this.parseQueries(element);
if (!queries.length) {
return;
}
var container = {
element: element,
queries: queries
};
this.containers.push(container);
this.observeContainer(container);
this.evaluateQueries(container);
},
parseQueries: function(element) {
var data = element.getAttribute('data-container');
if (!data) return [];
// Expected format: "sm:400px,md:768px,lg:1024px"
var queries = [];
var parts = data.split(',');
for (var i = 0; i < parts.length; i++) {
var query = parts[i].trim().split(':');
if (query.length === 2) {
queries.push({
name: query[0].trim(),
minWidth: parseInt(query[1])
});
}
}
return queries;
},
observeContainer: function(container) {
if (!window.ResizeObserver) {
// Fallback to window resize
var self = this;
window.addEventListener('resize', function() {
self.evaluateQueries(container);
});
return;
}
var self = this;
var observer = new ResizeObserver(function() {
self.evaluateQueries(container);
});
observer.observe(container.element);
},
evaluateQueries: function(container) {
var width = container.element.offsetWidth;
var activeQueries = [];
// Determine which queries match
for (var i = 0; i < container.queries.length; i++) {
var query = container.queries[i];
if (width >= query.minWidth) {
activeQueries.push(query.name);
}
}
// Apply classes
this.applyClasses(container.element, activeQueries);
},
applyClasses: function(element, activeQueries) {
// Remove old container query classes
var classes = element.className.split(' ');
var newClasses = classes.filter(function(cls) {
return cls.indexOf('cq-') !== 0;
});
// Add new classes
for (var i = 0; i < activeQueries.length; i++) {
newClasses.push('cq-' + activeQueries[i]);
}
element.className = newClasses.join(' ');
// Also set data attribute for CSS targeting
if (activeQueries.length) {
element.setAttribute('data-cq', activeQueries.join(' '));
} else {
element.removeAttribute('data-cq');
}
}
};
// Initialize on load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
ContainerQueriesPolyfill.init();
});
} else {
ContainerQueriesPolyfill.init();
}
// Usage:
// HTML: <div data-container="sm:400,md:768,lg:1024">
// <div class="card">Content</div>
// </div>
//
// CSS:
// @container (min-width: 768px) {
// .card { display: grid; }
// }
// /* Polyfill fallback: */
// [data-cq~="md"] .card { display: grid; }
// .cq-md .card { display: grid; }
Example: Container query units polyfill (cqw, cqh)
// Container query units (cqw, cqh) polyfill
var CQUnitsPolyfill = {
init: function() {
this.processContainers();
},
processContainers: function() {
var containers = document.querySelectorAll('[data-cq-units]');
for (var i = 0; i < containers.length; i++) {
this.processContainer(containers[i]);
}
},
processContainer: function(container) {
var elements = container.querySelectorAll('[style*="cqw"], [style*="cqh"]');
for (var i = 0; i < elements.length; i++) {
this.convertUnits(elements[i], container);
}
// Watch for size changes
this.observeContainer(container);
},
convertUnits: function(element, container) {
var style = element.getAttribute('style');
if (!style) return;
var containerWidth = container.offsetWidth;
var containerHeight = container.offsetHeight;
// Convert cqw (container query width)
style = style.replace(/(\d+(?:\.\d+)?)cqw/g, function(match, value) {
return (parseFloat(value) * containerWidth / 100) + 'px';
});
// Convert cqh (container query height)
style = style.replace(/(\d+(?:\.\d+)?)cqh/g, function(match, value) {
return (parseFloat(value) * containerHeight / 100) + 'px';
});
element.setAttribute('style', style);
},
observeContainer: function(container) {
if (!window.ResizeObserver) return;
var self = this;
var observer = new ResizeObserver(function() {
self.processContainer(container);
});
observer.observe(container);
}
};
// Initialize
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
CQUnitsPolyfill.init();
});
} else {
CQUnitsPolyfill.init();
}
Note: Container queries have native support in modern browsers. Use the
container-query-polyfill npm package for production or consider the @container PostCSS
plugin.
Key Takeaways - CSS & Styling
- CSS Variables: Use css-vars-ponyfill for IE11, parse and substitute at runtime
- Grid/Flexbox: Autoprefixer handles vendor prefixes, use float fallback for IE9
- calc(): Native support from IE9+, use preprocessors for complex calculations
- Media Queries: matchMedia polyfill for JS, Respond.js for IE8
- CSS4 Selectors: :has() requires JavaScript, :focus-visible has good polyfill
- Container Queries: ResizeObserver + class toggling, native support growing