, ,

No / Low Code Provider Pattern

Introduction

This post continues from the previous one on No/Low Code development.

In this part, I’ll focus on the Provider Pattern, with an emphasis on generating HTML and CSS.

While the earlier example was intentionally simple, this time I want to explore more complex scenarios—such as defining widgets or entire screens with non-linear layouts. I’ll still keep the code snippets concise, but detailed enough to clearly convey the core concepts.

Example

ENTITY: Person, 1.0.1, "Person entity"
  HORIZONTAL_STACK: gap=1
    fullName: STRETCH
    active

  HORIZONTAL_GRID: height=auto
    COLUMN: width=50%
      firstName
      
    COLUMN: width=50%
      lastName
      
  notes: width=100% height=20

In this example, we’re defining two core elements:

  • The layout
  • Some basic style attributes

This approach is framework-agnostic and can be applied in a variety of contexts, including:

  • React
  • Vue
  • Vanilla JS
  • Bootstrap
  • Tailwind

Yes, I know—I’m skipping over a few hundred other frameworks in between… 😊
These examples highlight a diverse range of potential solutions.

Some are full-fledged frameworks that come with built-in styling or support for theming. Others, like Tailwind, are utility-first CSS frameworks where styles are applied directly within the HTML markup—making the structure and styling closely tied.

Provider Pattern

The Provider Pattern – A Quick Overview

The Provider Pattern is a design approach that decouples the definition of data or behavior from the implementation that uses it. Instead of hardcoding logic for a specific output, you define a common interface or contract, and then inject a “provider” that knows how to handle the specifics of that context.

How It Helps with DSL-Based Generation

When working with a Domain-Specific Language (DSL)—for example, to describe UI components, layouts, or styles—you don’t want to tie that DSL to a single output format like React or Tailwind. Instead, you define your UI declaratively in the DSL, and then use a provider to interpret that definition into the target format.

This opens up powerful possibilities:

  • React Provider: Translates your DSL into React components.
  • Tailwind Provider: Converts layout/style definitions into utility classes in markup.
  • React + Tailwind Provider: Combines the two, generating React components styled with Tailwind classes.

Each provider interprets the same DSL differently, depending on the target platform or style system. This makes your system modular, extensible, and future-proof—you can add new output types without rewriting the core logic.

Extending Styles

There are scenarios where you may want to apply custom styles to a specific instance of your DSL—perhaps to highlight validation errors, indicate focus, or adjust layout for particular contexts.

Here’s an example of how you can override the default styling directly within the DSL definition:

ENTITY: Person, 1.0.1, "Person entity"
  HORIZONTAL_STACK: gap=1
    fullName
    active

  HORIZONTAL_GRID: height=auto
    COLUMN: width=50%
      firstName
      
    COLUMN: width=50%
      lastName
      
  notes: width=100% height=20
  
  STYLE:
    firstName: background=VAR(cl-error-bg) foreground=VAR(cl-error-fg)      
    firstName:focus background=VAR(cl-success-bg) foreground=VAR(cl-success-fg)

In this example:

  • The STYLE block defines custom visual styling for specific fields—in this case, firstName.
  • The VAR(...) syntax references variable-based values (e.g. colors or tokens from a design system), making the styles flexible and theme-aware.
  • The second style includes a state-based selector (:focus), which allows conditional styling based on UI states.

While the syntax might resemble CSS, it’s important to avoid relying too heavily on web-centric terminology.
Even though this example targets the web, the goal is to keep the DSL platform-agnostic, so it can just as easily be used to generate native applications—like C# WinForms, JavaFX, or SwiftUI interfaces.

A Note on Common UI States

Cross-platform UI design often includes shared interaction states. Your DSL can support these universally, regardless of the rendering target:

  • focus – when the input is selected or active
  • hover – when the user points to an element
  • error – to highlight validation or system issues
  • disabled, selected, active, etc.

By modeling these as standard state extensions, your providers can interpret them contextually for any platform—whether that’s applying :hover in CSS, or binding to onFocus and onBlur events in React or native frameworks.

Summary

This post expands on No/Low Code development by introducing the Provider Pattern as a way to generate HTML and CSS from a domain-specific language (DSL). By decoupling layout and style definitions from specific frameworks like React or Tailwind, the Provider Pattern enables modular and extensible UI generation across platforms. The article showcases how a simple DSL can describe complex layouts and styles—including dynamic states like focus and error—and how providers translate these into platform-specific implementations, whether web or native.

Integrating providers within a Domain-Specific Language (DSL) significantly enhances the system’s modularity and adaptability. This approach not only facilitates the generation of HTML and CSS but also extends to various outputs supported by the DSL, ensuring a clean and maintainable code environment. By decoupling the core logic from specific implementations, providers enable seamless integration with multiple frameworks and platforms, thereby broadening the scope and longevity of the system.