JSON in CSS

A Modern Approach to Dynamic Styling. Learn how to leverage JSON data structures to organize CSS configurations, create dynamic theming systems, and build more maintainable stylesheets with CSS custom properties.

Understanding CSS Custom Properties

CSS custom properties introduce a native way to define reusable values in CSS. Unlike preprocessor variables such as Sass variables, CSS custom properties are dynamic and can be modified at runtime through JavaScript, and they participate in the cascade. This makes them ideal for implementing features like user-selectable themes, responsive typography, and accessibility preferences.

What Are CSS Custom Properties?

CSS custom properties represent specific values to be reused throughout a document. Property names begin with two dashes (--) followed by the property name, and values are retrieved using the var() function. This native CSS feature has been widely supported across all modern browsers since 2017.

The fundamental syntax for declaring a custom property involves prefixing the property name with two dashes:

:root {
 --primary-color: #3b82f6;
 --font-size-base: 16px;
 --spacing-unit: 1rem;
}

Once defined, these properties can be referenced anywhere in your stylesheet using the var() function:

button {
 background-color: var(--primary-color);
 font-size: var(--font-size-base);
 padding: var(--spacing-unit) calc(var(--spacing-unit) * 2);
}

Inheritance and Cascade Behavior

One of the most powerful aspects of CSS custom properties is their inheritance behavior. Unlike preprocessor variables, which are static at compile time, CSS custom properties flow through the DOM and can be redefined at any level. This inheritance model enables sophisticated theming patterns where you can override specific properties for components or sections without affecting the global theme.

For example, you might define a global color palette in the :root selector and then override specific colors within a particular component context. This powerful scoping mechanism enables features like dark mode, where adding a class to the body element automatically updates all descendant elements using those custom properties.

The var() Function and Fallback Values

The var() function retrieves the value of a custom property and accepts an optional fallback value as its second argument. The fallback is used when the custom property hasn't been defined or contains an invalid value:

.card {
 background-color: var(--card-background, #ffffff);
 border-color: var(--border-color, var(--neutral-300, #d1d5db));
}

This fallback mechanism is particularly valuable when incrementally adopting custom properties in an existing codebase or when supporting multiple theme versions. It ensures graceful degradation and progressive enhancement for users with different capabilities.

JSON as a CSS Configuration Format

JSON has become the de facto standard for representing design tokens and style configurations in modern design systems. Design tools like Figma, with token plugins, commonly export design decisions as JSON, making it natural to consume these configurations directly in your CSS or JavaScript tooling. This structured, machine-readable format bridges the gap between design and development workflows.

For teams exploring type-safe CSS solutions, combining JSON configurations with tools like CSS in TypeScript with Vanilla Extract provides an additional layer of compile-time safety while maintaining the flexibility of JSON-based design tokens.

Why JSON for CSS?

JSON provides a structured approach to defining style configurations that can be easily shared between design tools, design systems, and your application's codebase. TypeScript interfaces can provide compile-time type safety for JSON configurations, ensuring consistency and catching errors early in development. A typical JSON configuration for a design system might include colors, typography, spacing, and other foundational properties organized hierarchically.

{
 "colors": {
 "primary": "#3b82f6",
 "secondary": "#6366f1",
 "neutral": {
 "100": "#f3f4f6",
 "500": "#6b7280",
 "900": "#111827"
 }
 },
 "spacing": {
 "sm": "0.5rem",
 "md": "1rem",
 "lg": "1.5rem"
 },
 "typography": {
 "fontSize": {
 "base": "1rem",
 "lg": "1.125rem"
 }
 }
}

Converting JSON to CSS Custom Properties

Converting JSON configurations to CSS custom properties requires flattening nested structures and applying appropriate naming conventions. A common approach uses JavaScript to recursively process the JSON object and generate CSS variable declarations that can be dynamically injected into the document at runtime. This approach enables features like theme switching without requiring a page reload.

When implementing JSON-to-CSS conversions, consider the performance implications discussed in our guide on CSS selector performance, as efficient selector usage complements well-structured CSS architecture:

function jsonToCssVariables(json) {
 const lines = [':root {'];

 function processObject(obj, path = []) {
 for (const [key, value] of Object.entries(obj)) {
 const variableName = [...path, key].join('-').toLowerCase();

 if (typeof value === 'object' && value !== null) {
 processObject(value, [...path, key]);
 } else {
 lines.push(` --${variableName}: ${value};`);
 }
 }
 }

 processObject(json);
 lines.push('}');
 return lines.join('\n');
}

const css = jsonToCssVariables(themeConfig);
document.head.insertAdjacentHTML('beforeend', `<style>${css}</style>`);

JavaScript APIs for Working with CSS and JSON

Modern web applications require the ability to read, write, parse, and serialize styles dynamically. The browser provides native JavaScript APIs that bridge CSS custom properties with JSON data, enabling powerful styling capabilities for interactive applications.

Reading CSS Custom Properties

To read the current value of a CSS custom property from JavaScript, you use the getComputedStyle() method combined with getPropertyValue(). This method returns the computed value, which reflects any runtime changes that may have occurred:

function getCssVariable(variableName) {
 const computedStyle = getComputedStyle(document.documentElement);
 return computedStyle.getPropertyValue(variableName).trim();
}

const primaryColor = getCssVariable('--colors-primary');

This approach is essential for implementing features like theme customizers where you need to synchronize UI state with CSS. The values are returned as strings, so numeric values may require parsing for mathematical operations.

Writing CSS Custom Properties

The style.setProperty() method provides direct access to modify CSS custom property values at runtime, with changes applying immediately to all elements using that property. Changes can be scoped to specific elements or applied globally to the document root:

function setCssVariable(variableName, value, element = document.documentElement) {
 element.style.setProperty(variableName, value);
}

function toggleDarkMode() {
 const isDark = document.body.classList.toggle('dark-mode');
 
 if (isDark) {
 setCssVariable('--colors-bg-primary', '#111827');
 setCssVariable('--colors-text-primary', '#f9fafb');
 } else {
 setCssVariable('--colors-bg-primary', '#ffffff');
 setCssVariable('--colors-text-primary', '#1f2937');
 }
}

Parsing JSON for Style Configurations

Loading JSON configurations into your application involves fetching the JSON string and parsing it into a JavaScript object using JSON.parse(). This parsed object can then be used to generate CSS or directly manipulate styles. Validation should occur before processing, and TypeScript provides compile-time safety for JSON configurations:

async function loadThemeFromJson(url) {
 const response = await fetch(url);
 const jsonString = await response.text();
 const themeConfig = JSON.parse(jsonString);
 
 applyThemeConfig(themeConfig);
 return themeConfig;
}

Serializing Styles to JSON

For features that require saving or exporting style configurations, you can serialize JavaScript objects back to JSON using JSON.stringify(). This is useful for saving user customizations, syncing state across sessions, or sharing theme configurations. Circular references must be avoided when serializing DOM elements:

function captureCurrentTheme() {
 const root = getComputedStyle(document.documentElement);
 const theme = { colors: {} };
 
 theme.colors.primary = root.getPropertyValue('--colors-primary').trim();
 return JSON.stringify(theme, null, 2);
}
Reading CSS Variables
1// Reading CSS Custom Properties2function getCssVariable(variableName) {3 const computedStyle = getComputedStyle(document.documentElement);4 return computedStyle.getPropertyValue(variableName).trim();5}6 7// Usage8const primaryColor = getCssVariable('--colors-primary');9console.log(primaryColor); // "#3b82f6"
Writing CSS Variables
1// Writing CSS Custom Properties2function setCssVariable(variableName, value, element = document.documentElement) {3 element.style.setProperty(variableName, value);4}5 6// Theme switcher example7function toggleDarkMode() {8 const isDark = document.body.classList.toggle('dark-mode');9 10 if (isDark) {11 setCssVariable('--colors-bg-primary', '#111827');12 setCssVariable('--colors-text-primary', '#f9fafb');13 } else {14 setCssVariable('--colors-bg-primary', '#ffffff');15 setCssVariable('--colors-text-primary', '#1f2937');16 }17}

Practical Applications

The real value of JSON-CSS integration becomes apparent when building real-world applications. From design system implementation to user customization features, these techniques enable sophisticated styling capabilities that would be difficult to achieve with traditional CSS approaches.

Design Token Integration

Design tokens represent the atomic values in a design system, including colors, typography, spacing, and other foundational properties. JSON has emerged as the standard format for design token exchange, with organizations like the Design Tokens Community Group establishing formats for representing tokens. Integrating these tokens into your CSS workflow through custom properties ensures consistency between design and development:

{
 "color": {
 "primitive": {
 "blue": {
 "500": { "value": "#3b82f6" },
 "600": { "value": "#2563eb" }
 }
 },
 "semantic": {
 "primary": { "value": "{color.primitive.blue.500}" },
 "primary-hover": { "value": "{color.primitive.blue.600}" }
 }
 }
}

Maintaining the token-source relationship enables seamless updates when design decisions change, propagating updates across all platforms and touchpoints.

Dynamic Theme Switching

Multiple themes can coexist as separate JSON configurations, with CSS custom properties enabling instant switching without page reloads. This approach supports features like user-selectable color schemes, high-contrast modes for accessibility, and seasonal themes. User preferences can be persisted in localStorage or cookies for consistent experiences across visits:

const themes = {
 light: {
 colors: {
 background: '#ffffff',
 text: '#1f2937',
 primary: '#3b82f6'
 }
 },
 dark: {
 colors: {
 background: '#111827',
 text: '#f9fafb',
 primary: '#60a5fa'
 }
 }
};

function switchTheme(themeName) {
 const theme = themes[themeName];
 const root = document.documentElement;
 
 for (const [key, value] of Object.entries(theme.colors)) {
 root.style.setProperty(`--colors-${key}`, value);
 }
 
 localStorage.setItem('selected-theme', themeName);
}

User Customization Systems

Building theme customizers that allow users to modify colors, fonts, and spacing with real-time preview requires connecting form inputs to CSS custom properties. The customization can be exported as JSON for backup or sharing, enabling users to take their personalized themes with them. Input validation prevents invalid CSS values from breaking the interface:

function initializeThemeCustomizer() {
 const colorPicker = document.getElementById('primary-color-picker');
 
 colorPicker.addEventListener('input', (event) => {
 document.documentElement.style.setProperty(
 '--colors-primary',
 event.target.value
 );
 });
}

This approach can be extended to support team-based design systems where different departments or clients require tailored visual experiences managed through shared JSON configurations.

Dynamic Theme Switching
1const themes = {2 light: {3 colors: {4 background: '#ffffff',5 text: '#1f2937',6 primary: '#3b82f6',7 secondary: '#6366f1'8 }9 },10 dark: {11 colors: {12 background: '#111827',13 text: '#f9fafb',14 primary: '#60a5fa',15 secondary: '#818cf8'16 }17 },18 highContrast: {19 colors: {20 background: '#000000',21 text: '#ffff00',22 primary: '#00ff00',23 secondary: '#00ffff'24 }25 }26};27 28function switchTheme(themeName) {29 const theme = themes[themeName];30 if (!theme) return;31 32 const root = document.documentElement;33 for (const [key, value] of Object.entries(theme.colors)) {34 root.style.setProperty(`--colors-${key}`, value);35 }36 37 localStorage.setItem('selected-theme', themeName);38}

Performance Considerations

While CSS custom properties provide powerful capabilities, understanding performance implications ensures smooth user experiences. Proper techniques minimize layout thrashing and optimize rendering performance for dynamic styling features.

Minimizing Layout Thrashing

When updating CSS custom properties frequently, such as in animations or drag operations, performance becomes critical. CSS custom properties can be animated using the @keyframes at-rule or the Web Animations API, providing hardware-accelerated transitions. For JavaScript-driven updates, batching writes and using requestAnimationFrame prevents layout thrashing:

function animateValue(propertyName, start, end, duration) {
 const startTime = performance.now();

 function update(currentTime) {
 const elapsed = currentTime - startTime;
 const progress = Math.min(elapsed / duration, 1);
 const eased = 1 - Math.pow(1 - progress, 3);
 const currentValue = start + (end - start) * eased;
 document.documentElement.style.setProperty(propertyName, currentValue);

 if (progress < 1) {
 requestAnimationFrame(update);
 }
 }

 requestAnimationFrame(update);
}

Avoid reading computed styles immediately after writing, as this forces synchronous layout calculations that can impact performance significantly.

Selective Property Application

Only update properties that have changed rather than reapplying entire theme configurations. For components with unique styling needs, scoping custom properties to specific elements instead of :root improves performance by reducing the number of elements that need to be updated. Consider using CSS containment for performance isolation and measure impact with browser DevTools Performance panel:

function applyScopedTheme(container, theme) {
 const element = container instanceof Element ? container : document.querySelector(container);
 if (!element) return;

 for (const [key, value] of Object.entries(theme)) {
 element.style.setProperty(`--theme-${key}`, value);
 }
}

applyScopedTheme('.dashboard', {
 background: '#1e293b',
 panelBackground: '#334155'
});

These techniques become especially important when building interactive applications with real-time theme previews or responsive design systems that adapt to user interactions.

Best Practices

Following established guidelines for organizing and maintaining CSS custom properties ensures scalable, maintainable styling systems. These practices help teams collaborate effectively and prevent common pitfalls in dynamic styling implementations.

Naming Conventions

Establishing clear naming conventions for CSS custom properties makes your codebase more maintainable and helps prevent naming conflicts. Use descriptive, semantic names following a consistent hierarchy such as category-subcategory-name pattern. Avoid overly generic names that might conflict across different components or modules:

:root {
 /* Color palette - semantic names */
 --color-primary: #3b82f6;
 --color-primary-hover: #2563eb;
 
 /* Spacing - consistent scale */
 --spacing-xs: 0.25rem;
 --spacing-sm: 0.5rem;
 --spacing-md: 1rem;
 
 /* Typography */
 --font-size-sm: 0.875rem;
 --font-size-base: 1rem;
 --font-size-lg: 1.125rem;
}

Organization and Documentation

Group related custom properties logically and document their purpose and expected values. Using CSS comments to explain usage patterns helps team members understand available options and prevents misuse. Keep the number of root-level properties manageable through organization and consider creating functional groups for different aspects of your design system.

Fallback Strategies

While CSS custom properties are widely supported, providing fallbacks ensures a graceful experience for all users. Always provide sensible fallbacks for critical properties, using the cascade to define base styles before custom properties. Test with JavaScript disabled to ensure graceful degradation, and consider older browsers that may have limited custom property support:

.button {
 background-color: #3b82f6;
 background-color: var(--color-primary, #3b82f6);
 
 color: white;
 color: var(--color-white, #ffffff);
 
 border-radius: 0.5rem;
 border-radius: var(--radius-md, 0.5rem);
}

By following these practices, you can build maintainable CSS architectures that scale with your projects and support modern frontend development workflows.

CSS Custom Properties Best Practices
1:root {2 /* Color System */3 --color-primary: #3b82f6;4 --color-primary-hover: #2563eb;5 --color-secondary: #6366f1;6 --color-success: #10b981;7 --color-warning: #f59e0b;8 --color-error: #ef4444;9 10 /* Neutral Colors */11 --color-neutral-50: #f9fafb;12 --color-neutral-100: #f3f4f6;13 --color-neutral-500: #6b7280;14 --color-neutral-900: #111827;15 16 /* Typography */17 --font-family-base: 'Inter', system-ui, sans-serif;18 --font-size-sm: 0.875rem;19 --font-size-base: 1rem;20 --font-size-lg: 1.125rem;21 22 /* Spacing */23 --spacing-2: 0.5rem;24 --spacing-4: 1rem;25 --spacing-6: 1.5rem;26 --spacing-8: 2rem;27 28 /* Effects */29 --radius-md: 0.5rem;30 --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);31 --transition-fast: 150ms ease;32}33 34/* Fallback strategies */35.button {36 background-color: #3b82f6;37 background-color: var(--color-primary, #3b82f6);38 39 color: white;40 color: var(--color-white, #ffffff);41 42 border-radius: 0.5rem;43 border-radius: var(--radius-md, 0.5rem);44}

Conclusion

CSS custom properties combined with JSON configurations provide a powerful system for managing styles in modern web applications. They enable dynamic theming without page reloads, seamless design token integration between design tools and codebases, and user customization features that adapt to individual preferences. The native browser APIs make it straightforward to read, write, and serialize CSS variables, while performance considerations like batching updates and selective property application ensure smooth user experiences.

Following best practices for naming conventions, organization, and fallbacks ensures maintainable and accessible implementations that scale with your projects. Whether you're building a design system that syncs with Figma tokens, implementing dark mode support, or creating theme customizers for your users, understanding how to bridge JSON and CSS is an essential skill for contemporary frontend development.

As CSS tooling continues to evolve, many teams are exploring alternatives to traditional preprocessors. Our analysis of CSS preprocessor popularity shows growing interest in native CSS features like custom properties over compile-time solutions.

For organizations building comprehensive web applications, these techniques integrate naturally with our web development services and support maintainable frontend architectures. When combined with proper design system implementation, teams can achieve consistency across all touchpoints while enabling flexibility for customization.

Sources

  1. MDN Web Docs - Using CSS custom properties
  2. MDN Web Docs - Custom properties (--*)
  3. Featureblend - CSS JSON
  4. W3Schools - JSON.parse()

Ready to Modernize Your Styling Workflow?

Our team specializes in building maintainable, performant frontend architectures using modern CSS techniques and design systems.