[Meta] Make Drupal the first "design-system native" CMS + Unify & simplify render & theme systems

Created on 30 June 2025, 4 days ago

Problem/Motivation

Lackluster contrib space for themes

This is one of the symptoms of a few problems with Drupal theming:

  • Themes are not sharable, because we are theming a project instead of implementing a design. For example, adding a node--warehouse.html.twig template will be useful only for project with the exact same warehouse content type.
  • If shared, a theme is not "plug and play", for the same reason. There is always a template missing, because of the new config we are building for a project. For example, where is my media--perfume.html.twig?
  • The DX (developer experience) is not friendly. Too much drupalisms. For example, what the heck is taxonomy-term.html.twig? Sometimes, PHP is needed 😱

Business agnostic coding

Modules don't have those issues because they are business agnostic:

  • no business in PHP code which is exposing swappable, shareable, reusable Drupal plugins
  • all business in config entities (content types, views, contribution workflow, text format, displays...)
  • business (config entity, site building) is calling the code (plugin attachment), not the other way around

Themes need the same logic: provide configurable plugins we can attach to our displays.

So, we need to inverse the workflow by asking the front-dev to implement a design (using Drupal's YAML plugin discoveries) instead of theming a Drupal project. Front-developer is becoming a plugin provider without realizing it:

Thanks to those additional YAML files, Drupal specific but describing only the UI implementation, front-developer is enjoying ownership & empowerment.

So, what would be the plugin types? How can we make the YAML front-dev friendly?

Phase 1, 2025: Add missing theming API

We already have the solution in Drupal Core, because we are already asking the front-dev to implement design system parts with plugins YAML discovery:

Design systems are a design conceived and implemented in a business/project agnostic way. One UI design = many projects/products. So, their philosophy fits with our goal of being sharable between Drupal projects.

We need to complete with missing design system parts:

And improve what we already have:

In order to allow front-developers to implement full, ready to use, designs into a Drupal theme.

This is also impacting the Render API by adding new renderables and render properties which are not describing the CMS application but the design implementation :

Already in Drupal Core:

Proposed for Drupal 11.3 (work in progress, may change):

Phase 2, 2026: Simplifying Render & Form APIs

Those additions will allow us to solve a 10 years old issue : Drupal's render & theme system are too complex to use. See: 🌱 Unify & simplify render & theme system: component-based rendering (enables pattern library, style guides, interface previews, client-side re-rendering) Active

This is not the first time adopting design system methodology is expected to help us fixing desperate issue: 🐛 SDC: Make empty render arrays evaluate to false in component templates Active to address 🌱 [meta] Themes improperly check renderable arrays when determining visibility Needs work

Current status

Let's not forget the Render API is great:

  • Declarative: Easy to type. (de)Serializable if clean.
  • Easy nesting: The Virtual DOM of Drupal. We are building a tree.
  • Data bubbling: Declare locally, impact globally
  • Asset libraries management: Our beloved libraries.yml
  • Clever caching: Context, tags, keys…

Building displays by assembling configurable plugins returning renderables is so nice.

However, the renderables themselves are an issue. They are too many of them and they are of 3 kinds:

  • 35 render elements in Core:
    $ grep -hoEr "#type' => '(\S+)'" core/ --exclude-dir tests | sort | uniq -c | sort -nr
    138 #type' => 'details'
      94 #type' => 'container'
      93 #type' => 'link'
      68 #type' => 'table'
      47 #type' => 'actions'
      36 #type' => 'inline_template'
      27 #type' => 'fieldset'
      23 #type' => 'html_tag'
      21 #type' => 'status_messages'
      14 #type' => 'pager'
      …
    
  • 99 theme hooks in Core:
    $ grep -hoEr "#theme' => '(\S+)'" core/ --exclude-dir tests | grep -v "__" | sort | uniq -c | sort -nr
    84 #theme' => 'item_list'
    21 #theme' => 'username'
    16 #theme' => 'image'
      7 #theme' => 'status_messages'
      7 #theme' => 'image_style'
      6 #theme' => 'table'
      6 #theme' => 'links'
      5 #theme' => 'update_version'
      5 #theme' => 'indentation'
      5 #theme' => 'file_upload_help'
      …
    
  • 2 special ones:
       1016 '#markup'
           85 '#plain_text'
    

😱 135 renderables to learn/use? So confusing and error prone.

95% of renderables can be deprecated

We have SDC for UI components now so:

  • #type => component can replace a lot of hook_theme, the ones describing UI components: breadcrumb, progress_bar, links...
  • We can also replace the 34 render elements which are wrappers around hook_theme (what they do, adding asset libraries, variable typing... is already done by SDC) we removed : table, status_messages, pager...

The hook_theme not suitable for conversion to SDC are often annoying CMS-related wrappers we can get rid of anyway : block, node, view, field

The few remaining render elements can't be expressed as UI components:

  • some must be kept because they represent other design systems artefacts (icons...) or low level tools (HTML elements, placeholders...)
  • some may (not found in my today look) be shortcut to Drupal callables returning renderables, it is better to explicitly call the callable (service, function, method..) instead

Deprecation of many Twig additions

Also, it may be the opportunity to challenge the various additions Drupal made to Twig last 10 years.

Most of the Drupal-specific Twig functions added in 2015, during the development of Drupal 8.0, are requesting the Drupal application state:

Other functions have their own kind of issues:

Some filters have similar issues (|render(), |add_suggestion()...)

Those functions were great and pragmatic when they were implemented 10 years ago, they made the conversion from PHP Templates (using functions like theme_link()) to Twig easier and the community-wide transition a success.

However, they may not fit with the new, design-system oriented, paradigm started with SDC, where the UI component needs to encapsulate business-agnostic UI-logic only and stays "dumb" & stateless. On any website, any context, with the same values injected in slots & props, always return the same markup.

We can dream in a future situation where only 5 additions will stay relevant:

  • the PrintNode visitor to send all printed variable to the renderer service. That's the core of Drupal Twig rendering, it works very well and must be kept.
  • Attribute object and the related create_attribute() function, but let's hope we will be able to get rid of it someday: HTML attributes as Twig mappings instead of PHP obejcts Active
  • add_class() & set_attribute() filters, they make sense used with the SDC slots.
  • the new icon() function for the Icon API. It follows Twig philosophy of using functions to generate printable data
  • the translations helpers (t function, trans tag). Important feature for a CMS. Why not proposing this to upstream Twig?

Deprecation of ThemeManager::render()

Because SDC is not passing through the ThemeManager, if we reach a situaton where all template-based renderables are managed by SDC, we don't need the template loading feature of the ThemeManager anymore.

So we get rid of it and its related mechanisms:

  • Theme wrappers: Confusing and useless. You can always use an upper level instead.
  • Templates suggestions: Not discoverable. Messy. Blur the business / view separation.
  • Preprocess hooks: Risky. Unfriendly. Blur the business / view separation. Unpredictable order of execution.

@catch is alerting us about the complexity of this ambitious task:

The projects might not be using them directly, but there are dozens/hundreds of preprocess hooks running on those projects via core and contrib modules. https://api.drupal.org/api/drupal/core%21modules%21node%21node.module/fu... is a monster just by itself. edit: or https://api.drupal.org/api/drupal/core%21modules%21system%21system.admin...

Removing just one variable is non-trivial, although at least we have deprecation support now.

Conclusion

We can have equally useful Render & Form APIs with less than 15 renderables:

  • Design systems artefacts:
  • Lower level tools:
    • #type => inline_template
    • #type => html_tag : for HTML elements
    • #type => link
    • #markup
    • #plain_text
    • #lazy_builder
  • Non-UI form elements: #type => submit, #type => value, #type => item...

And a few, maybe less than 15 too, "shared" render properties:

  • For render processing: #attached, #cache...
  • Related to design systems (WIP proposals): #styles, #tokens, #mode...
  • For form elements processing: #name, #default_value, #required...

And now we have an purely design/UI based Render API!

If we do that, step by step, deprecating slowly, not breaking anything, the display building tools from Core and Contrib will have a simpler implementation and will have an easier time introducing new features.

User interface changes

No

Introduced terminology

Sometimes, see specific issues.

API changes

Yes, we deprecate a lot of things.

🌱 Plan
Status

Active

Version

11.0 🔥

Component

theme system

Created by

🇫🇷France pdureau Paris

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @pdureau
  • 🇨🇭Switzerland 4aficiona2

    Great if Drupal (finally) goes this way and has ambitous goals in being a "Design System first" CMS.

    Maybe more "niche" stuff in the future (animations/transitions, (buildtime) design tokens...)

    This is in my opinion an absolute must and basis to have a Design Token API if we want to call Drupal a "Design System first" CMS. Design Tokens fuel everything and are the most "atomic" design decisions — especially if they are also system/language/type agnostic.

    There are quite good tools out there like https://styledictionary.com/ to also transpile/convert them to the wished format and having "CSS variables / custom properties / runtime tokens" availabe is absolute necessary.

    Imo Design Tokens are a corner stone of a "Design System first" CMS :)

  • 🇬🇧United Kingdom catch

    Overall this looks great to me, there are thing that seem very obvious and others that aren't as clear, but I am not a design systems person so I'm really just looking at it for what the concrete changes for existing things would be.

    I think it would be useful to try the theme/render -> SDC path for one (non-form) element so that we can hopefully flush out what the issues are with conversions, deprecation etc.

    For me an example which you would think would be simple but unfortunately is not, is item_list. We already have multiple issues open trying to clean that up going back to at least 2008:

    📌 Replace links.html.twig with item-list--links.html.twig Needs work
    Replace .info.yml with composer.json for extensions Postponed
    📌 Remove title and wrapper div from item-list.html.twig Needs work
    #2032695: Use item-list.html.twig instead of views_view_summary.html.twig

    Non-exhaustive list of questions for me would be things like:

    1. What does the new SDC for item lists look like?
    2. What is the relationship between this and the replacement for #theme => links?
    3. Can the duplicate views template just use the item_list SDC or does views need its own version for some reason?
    4. What happens with existing uses of item_list that switch to the SDC but which are relying on theme suggestions.

    If we go through this for one element/theme function (including the duplicates and near duplicates and the usages) from start to finish, then it would hopefully make it easier to go through the process for all the other ones.

  • 🇫🇷France pdureau Paris

    Hi @4aficiona2

    That's great to see another design system lover here 👍

    This is in my opinion an absolute must and basis to have a Design Token API if we want to call Drupal a "Design System first" CMS. Design Tokens fuel everything and are the most "atomic" design decisions — especially if they are also system/language/type agnostic.

    I agree (buildtime) Design Tokens have a central part in design system methodology. I called them "niche" in the issue description because the goal here is to make Drupal able to use an already built design system implementation.

    "Working with a design system" as a different meaning according to the persona and the step:

    1. As a designer, I am designing the system, and I set variables in my design tools to organize my work. Some of those variables can be shared as design tokens.
    2. As a front-developer, I am implementing the design system, and I set variables in my CSS preprocessor (so, as Saas variables, Less Variables, Tailwind's @apply directives...). Those variables could also be considered as design tokens, retrieved from the ones shared by the designer. They are resolved at build.
    3. As a back-developer or site builder, I am using the design system, managing my Drupal displays using what was built by the front-developers from the (buildtime) design tokens:
      • UI components
      • Icon packs
      • style utilities (in Tailwind, by design, each style option is also a design token but that's not the case in other design systems I have studied)
      • CSS variables (which are an subset of the design tokens which are overridable at runtime)
      • ...

    The current issue is not for implementing a design system, but for using it. So, it is focused on the back-developer and site builder personas, to let them use the stuff built by the front-dev from design tokens, shipped i na Druapl theme, rendered by the browser. So, I don't see how the design tokens, which have already "disappeared" at this step, are fitting.

    Visual workflow:

    Anyway, I am not closing the door here. There may be something to do at the "using a design system" step, if I follow the hopes of some brilliant Drupalers:

    There are quite good tools out there like https://styledictionary.com/ to also transpile/convert them to the wished format and having "CSS variables / custom properties / runtime tokens" available is absolute necessary.

    This tools looks great, it can be useful for the front-devs which want to expose some of the design tokens as CSS variables. It is a design system implementation decision.

    To build our Drupal displays, why not using directly the CSS variables? So, we benefit from 2 strategic decisions made by the front-dev who:

    • have already picked the tokens to expose at runtime, instead of struggling with myriad of obscure tokens
    • set the default values according to the scopes ("scope" mechanism are not a thing with design tokens, it must be added at build time)
Production build 0.71.5 2024