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