Is CSS a programming language?

Is CSS a programming language?

November 29, 2024 8 minutes reading time Development tools css

Whether CSS qualifies as a programming language has long been a topic of debate, particularly among developers from different disciplines, such as frontend versus backend. To address this question, let’s dissect it from several angles: definitional, functional, semantic, and pragmatic. By the end, I try to synthesize these perspectives into a coherent conclusion.

I’m a backend developer specializing in building and maintaining backend services that integrate SQL databases and other APIs. I long thought that CSS was not a programming language. But recently I have reconsidered this idea. Mainly because I saw what “Fluid Layout” means and how e.g. functions like clamp and minmax can be used.

What Defines a Programming Language?

To determine if CSS is a programming language, we must first clarify what constitutes one. Generally, programming languages share the following characteristics:

  • Syntax and Structure: A defined set of rules and grammar for writing code.
  • Turing Completeness: The ability to represent a computation that can be performed by a Turing machine (e.g. loops, conditionals, and variables).
  • Logic and Control Flow: Mechanisms to make decisions and repeat operations (e.g. if conditions, for loops).
  • Abstraction: The ability to encapsulate logic into reusable components (e.g. functions or classes).
  • Manipulation of State: The ability to change or operate on data values over time.

CSS and These Characteristics:

  • Syntax and Structure: CSS undeniably has a well-defined syntax for styling HTML elements. Selectors, properties, and values form the core syntax.
  • Turing Completeness: CSS itself is not Turing complete because it lacks constructs like loops and conditionals in its original form. However, modern CSS has introduced functions and features (like calc(), clamp(), @media, and @supports) that allow for a more dynamic and conditional design, bringing it closer to computational logic.
  • Logic and Control Flow: CSS has conditional-like constructs in the form of media queries and feature queries (@media, @supports), but these do not represent general-purpose logic flow.
  • Abstraction: CSS supports abstraction to some extent via reusable rules (e.g. variables with --custom-properties, mixins in preprocessors like SASS, and shared class-based design patterns).
  • State Manipulation: CSS interacts with state indirectly (e.g. pseudo-classes like :hover, :focus, and :checked), but it doesn’t directly manipulate values or states in the way imperative languages do.

Thus, CSS satisfies some but not all characteristics of traditional programming languages.

CSS as a Declarative Language

CSS is generally categorized as a declarative language, meaning it describes what should happen rather than providing explicit instructions on how to make it happen. For example:

div {
    color: red;
    display: flex;
}

Here, you declare that div elements should be red and laid out as flex containers. You don’t specify how the browser engine achieves this result - this is abstracted away.

Declarative languages, including SQL are typically not considered programming languages in the strictest sense. They lack imperative constructs like loops or explicit state management. However, declarative languages are undeniably powerful tools in their respective domains.

Recent Enhancements to CSS

Modern CSS introduces features that challenge its purely declarative nature:

Functions, e.g. clamp, calc, and minmax

These functions allow you to perform calculations directly within the CSS, enabling dynamic behavior based on runtime conditions:

font-size: clamp(1rem, 2vw, 3rem);
grid-template-columns: minmax(250px, 1fr) 2fr;

These functions exhibit computational behavior, a hallmark of programming. However, they are limited to numerical computations and cannot branch or loop.

Conditional Logic, e.g. @media and @supports

Media queries @media and feature queries @supports provide conditional logic in CSS. An example:

.section {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1rem;

  @supports (display: masonry) {
    display: masonry;
    masonry-template-tracks: repeat(auto-fit, minmax(250px, 1fr));
  }
}

As you can see …

Whoa, whoa, whoa! Hold up, Code Captain! You just tossed out a CSS snippet like it’s a casual Tuesday, but let’s give this masterpiece the attention it deserves, shall we?
This isn’t just any grid - it’s a fancy-pants grid with a hidden agenda. Let’s unpack this brilliance, step by step!

Yes, you’re absolutely right. We should spend some time with this thing!

Let’s break down this CSS step by step to understand what each line does, including the use of the masonry feature:

Base Rules for the section element

.section {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1rem;
}
  • display: grid;
    • Sets the .section element to use CSS Grid for its layout.
    • CSS Grid is a powerful layout system that arranges content into rows and columns, allowing precise control over alignment and spacing.
  • grid-template-columns: repeat(auto-fit, minmax(250px, 1fr))
    • repeat(auto-fit, ...):
      • Automatically determines the number of columns that can fit in the available space.
      • auto-fit ensures that grid items expand to fill the row even if there aren’t enough items to fill all columns.
    • minmax(250px, 1fr):
      • Each column is at least 250px wide (the minimum size) but can expand to fill available space (up to 1fr), where fr stands for a fraction of the available free space.

This combination creates a responsive layout where the number of columns adjusts based on the container width, and columns are sized dynamically within the defined constraints.

  • gap: 1rem
    • Adds spacing of 1rem between grid items in both rows and columns.

Masonry-Specific Rules Inside @supports

@supports (display: masonry) {
  display: masonry;
  masonry-template-tracks: repeat(auto-fit, minmax(250px, 1fr));
}
  • @supports (display: masonry) { ... }
    • The @supports rule checks whether the browser supports the masonry value for the display property.
    • If masonry is supported, the rules inside the block are applied.
  • display: masonry
    • Sets the layout to masonry, a Firefox and Safari feature with some limitations at the time of writing.
    • Masonry layouts arrange items in a staggered, vertical pattern, much like how Pinterest displays content.
  • masonry-template-tracks: repeat(auto-fit, minmax(250px, 1fr))
    • Defines how the masonry layout organizes its columns:
      • repeat(auto-fit, ...): Similar to the grid layout, automatically determines the number of columns that can fit in the container.
      • minmax(250px, 1fr): Ensures each column is at least 250px wide but can expand proportionally to fill the remaining space (1fr).

This rule customizes the behavior of the masonry layout to align with the grid’s column definitions, ensuring a consistent visual structure regardless of whether masonry is supported.

Summary of Behavior

  1. Default Layout (Grid):

    • By default, the .section uses CSS Grid.
    • The number of columns dynamically adjusts based on the available space, ensuring responsive behavior.
    • Items are laid out in a strict row-and-column structure with equal gaps (1rem) between them.
  2. Enhanced Layout (Masonry):

    • If the browser supports the masonry display value, the layout switches to a masonry-style layout.
    • Items are stacked vertically in staggered columns, but the column sizes are still defined using the same minmax(250px, 1fr) logic.
  3. Fallback and Progressive Enhancement:

    • Browsers that do not support masonry fall back to the standard grid layout, ensuring the layout remains functional and visually appealing.

Key Advantages of This Code

  • Responsive Design: The use of repeat(auto-fit, minmax(...)) makes the layout responsive to container width.
  • Progressive Enhancement: The @supports block ensures the enhanced masonry layout is only applied in browsers that support it, while maintaining compatibility with other browsers.
  • Visual Consistency: Both the grid and masonry layouts share the same column-sizing logic, minimizing discrepancies between supported and unsupported browsers.

This code demonstrates modern CSS practices for creating flexible, responsive, and progressively enhanced layouts.

Now, back to our main text.

These constructs resemble if statements but operate declaratively - they describe what to apply under certain conditions, without actively controlling flow.

Pseudo-Classes and States

CSS pseudo-classes like :hover, :focus, and :nth-child() allow for dynamic styles based on user interaction or element position. While they can produce intricate designs, they don’t directly manipulate state or offer procedural control flow.

Preprocessors, e.g. SASS, LESS

While not CSS itself, preprocessors extend CSS with imperative constructs like loops, variables, and functions:

@for $i from 1 through 5 {
  .box-#{$i} {
    width: $i * 10px;
  }
}

This introduces procedural programming concepts but requires a compilation step before outputting standard CSS.

The Case for CSS as a Programming Language

If you reconsider the core purpose of CSS, you might argue it performs programming-like tasks. A programming language does not necessarily need to be imperative or Turing complete to be considered one - it must simply provide mechanisms to solve problems or express computations in its domain.

CSS increasingly enables developers to solve complex layout and styling problems programmatically:

  • The introduction of CSS Grid and Flexbox makes layout a computational exercise.
  • Functions like clamp() and calc() blur the lines between styling and computation.
  • The growing interactivity provided by pseudo-classes and conditional logic approaches stateful behavior.

From this perspective, CSS can be seen as a domain-specific programming language for styling.

The Pragmatic Perspective

Labels matter less than functionality. Even if CSS is not a “programming language” in the strictest sense, it has evolved into a powerful, essential tool for web development. It operates in tandem with HTML and JavaScript, and modern features make it more dynamic and computational than ever before.

For backend developers like myself, the growing sophistication of CSS feels closer to programming because its features now demand a deeper understanding of computational logic. Terms like “fluid layouts” and functions like clamp() require thought processes akin to solving problems with code.

But What About the Initial Question?

CSS is not traditionally classified as a programming language, as it lacks imperative control structures and state manipulation. However, it shares several characteristics of domain-specific programming languages (e.g. SQL) and has evolved to include computational and conditional capabilities.

If we loosen the definition of a programming language to include declarative and domain-specific tools, CSS could qualify as a programming-adjacent language. Whether you call it a programming language or not, its modern capabilities demand respect and computational thinking.