CSS custom properties (variables) have revolutionized how we approach styling on the web. While CSS doesn't provide built-in logical functions like not() or and(), we can emulate these operations using arithmetic calculations within the calc() function. This opens up powerful possibilities for creating adaptive, themeable interfaces that respond to multiple conditions simultaneously.
The ability to combine switch variables with logical operations separates intermediate CSS practitioners from advanced developers who can build truly dynamic styling systems.
Understanding Switch Variables
Before diving into logical operations, we need to understand the concept of a switch variable--a CSS custom property that holds either 0 or 1. This binary state forms the foundation for all logical operations we'll explore.
Defining Switch Variables
Switch variables are defined as any other CSS custom property, but their values are constrained to 0 (false/off) or 1 (true/on):
:root {
--wide-screen: 1;
--dark-mode: 0;
--sidebar-enabled: 1;
}
These variables can be toggled through various means:
- CSS custom property cascades
- Media queries
- Container queries
- State pseudo-classes like
:checked
For deeper exploration of CSS variable fundamentals, see our guide on CSS viewport units which demonstrates how variables integrate with responsive design patterns.
The Core Logical Operations
CSS provides no built-in logical functions, but we can achieve all fundamental operations using arithmetic in calc(). Each operation serves a specific purpose in building conditional styles.
| Operation | Formula | Returns 1 when |
|---|---|---|
| NOT | calc(1 - var(--switch)) | Switch is 0 |
| AND | calc(var(--a) * var(--b)) | Both A and B are 1 |
| NAND | calc(1 - var(--a) * var(--b)) | NOT both are 1 |
| OR | calc(1 - (1 - var(--a)) * (1 - var(--b))) | At least one is 1 |
| NOR | calc((1 - var(--a)) * (1 - var(--b))) | Both are 0 |
| XOR | calc((var(--a) - var(--b)) * (var(--a) - var(--b))) | A and B differ |
The NOT Operation
The NOT operation inverts a switch variable's value. If the input is 1, the output is 0, and vice versa.
Formula
--not-switch: calc(1 - var(--switch));
How It Works
| Input | Output |
|---|---|
--switch: 0 | --not-switch: 1 - 0 = 1 |
--switch: 1 | --not-switch: 1 - 1 = 0 |
Use Cases
The NOT operation is essential for creating complementary states, such as toggling between light and dark themes without duplicating stylesheets, or creating disabled states that are the inverse of enabled states.
The AND Operation
The AND operation returns 1 only if both operands are 1. If either operand is 0, the result is 0.
Formula
--and-result: calc(var(--switch-a) * var(--switch-b));
Why Multiplication?
Multiplying by 0 always produces 0, making multiplication the perfect operation for AND:
| A | B | A × B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
Use Cases
AND operations excel at creating compound conditions where multiple states must all be true. For example, enabling an advanced panel only when both the wide-screen breakpoint is active AND the user has explicitly enabled advanced controls.
The OR Operation
The OR operation returns 1 if at least one operand is 1. Only when both operands are 0 does the result become 0.
Formula
--or-result: calc(1 - (1 - var(--switch-a)) * (1 - var(--switch-b)));
Why Not Simple Addition?
At first glance, addition seems natural, but 1 + 1 = 2 which breaks our binary system. The solution leverages De Morgan's law: NOT (A OR B) = (NOT A) AND (NOT B).
Use Cases
OR operations are ideal for fallback scenarios--for instance, showing a sidebar if EITHER the user explicitly enabled it OR we're on a wide screen.
The XOR Operation
XOR (exclusive OR) returns 1 when the operands are different, and 0 when they are the same.
Formula
--xor-result: calc((var(--switch-a) - var(--switch-b)) * (var(--switch-a) - var(--switch-b)));
How It Works
- Subtraction gives us the difference:
0-0=0,0-1=-1,1-0=1,1-1=0 - Squaring (multiplying by itself) converts
-1and1to1, while0stays0
Use Cases
XOR is perfect for toggle states where you want the opposite behavior when two conditions differ--for example, alternating between two layouts when exactly one of two conditions is active.
Practical Example: Responsive Control Panel
Let's apply these concepts to a real-world scenario. We want an advanced control panel that should:
- Be hidden on narrow screens when disabled
- Be visible on wide screens regardless of enabled state
- Be visible when enabled, regardless of screen width
The CSS Implementation
body {
--wide: var(--wide-screen, 0);
}
.advanced-panel {
--enabled: var(--controls-enabled, 0);
--or: calc(1 - (1 - var(--wide)) * (1 - var(--enabled)));
margin: calc(var(--or) * 1rem) 0;
height: calc(var(--or) * 200px);
filter: opacity(calc(1 - var(--enabled) * 0.3));
}
By computing an OR value, we determine whether to show the panel. When either --wide or --enabled is 1, the panel becomes visible with full dimensions.
Performance Considerations
calc() Overhead
Each calc() computation adds slight processing overhead. For simple toggles, the impact is negligible. However, deeply nested calculations or animating values derived from logical operations can affect rendering performance.
Optimization Strategies
- Cache intermediate results: Store computed values in custom properties to avoid recalculation
- Use CSS containment: Apply
contain: layout paintfor complex components - Prefer transforms and opacity: These animate efficiently when derived from logical operations
- Test on target devices: Especially important for lower-powered mobile devices
Browser Compatibility
Modern browsers handle CSS variable calculations reliably. When supporting older browsers:
- Test thoroughly across target browsers
- Provide fallbacks using the cascade
- Use feature detection (
@supports) for critical functionality
Optimized CSS performance also contributes to better SEO performance, as page speed and rendering efficiency are key ranking factors for search engines.
Best Practices
Naming Conventions
Use clear, descriptive names for switch variables:
--is-enabled,--is-activefor binary states--feature-visible,--mode-darkfor feature flags--component-primary,--orientation-verticalfor component types
Organization
Group related logical operations for maintainability:
/* Logical operations - grouped together */
--not-enabled: calc(1 - var(--is-enabled));
--all-true: calc(var(--a) * var(--b) * var(--c));
--any-true: calc(1 - (1 - var(--a)) * (1 - var(--b)));
Debugging
When logical operations don't behave as expected:
- Log computed values using
color: rgb(var(--result), 0, 0) - Test each operation in isolation
- Verify switch variable values in browser dev tools
Conclusion
Logical operations with CSS variables transform custom properties from simple value storage into a powerful conditional system. By mastering NOT, AND, NAND, OR, NOR, and XOR operations using calc(), you can build adaptive interfaces that respond elegantly to multiple conditions without JavaScript.
This approach keeps stylesheets DRY, maintainable, and expressive--demonstrating the full potential of modern CSS. As CSS continues evolving with new capabilities like native conditionals, these foundational techniques will remain valuable for building sophisticated, maintainable styling systems for your web development projects.
CSS-Tricks' comprehensive guide on logical operations with CSS variables provides detailed formulas and additional techniques for advanced practitioners.
Frequently Asked Questions
Sources
- CSS-Tricks: Logical Operations with CSS Variables - Comprehensive guide covering all logical operations with detailed formulas
- MDN: Using CSS custom properties (variables) - Official documentation on CSS variable fundamentals