As of 3 hours ago (from the time of this posting), Drupal 10 has completely refactored off-canvas stylesheets! While the look and feel is the same, it’s now dramatically easier to customize, and will likely have a new design soon.
What’s the off-canvas dialog? It’s the little sidebar thing-a-doodle that pops out when you’re working with Layout Builder. In addition to Layout Builder, it’s used by the Settings Tray module, Workspaces module, and more. And, it can pop out from any direction.
Why refactor?
As with most problems in life, the problems started with Internet Explorer. More specifically, Drupal core’s commitment to support IE11.
Chief among the many problems to fix was a lack of a proper reset. The off-canvas dialog is an administrative component that is typically nestled within a front-end page. This means that styles would (and frequently did) leak down from the parent theme info the off-canvas dialog. When that happened, it polluted the visual styles causing all types of usability and accessibility issues. The reset was meant to prevent this from happening.
Examples of bad usability and accessibility include the tabledrag widget and drop-buttons, which were borderline non-functional in many front-end themes (including Olivero). Furthermore, focus styles were all over the place, which led to a host of WCAG accessibility violations.
In addition to a barely functional CSS reset, the off-canvas dialog suffered from a complicated CSS API for those poor souls that go down the road of attempting to override its look and feel.
- Not all elements were nested under the
#drupal-off-canvas
element - The selectors used were frequently overly specific and non-functional
- There was little to no abstraction of styles to utility classes
Another goal of the refactor was to make the off-canvas dialog easily themable through the use of CSS custom properties (aka CSS Variables) which can be easily overridden by a theme or module.
In addition, we wanted to make the new code easily readable and understandable for core developers down the road. This will make things easier to change in the future. And at some point, we’ll want new visual styles to match the new Claro theme.
How did we get here?
Because of the limitations of IE, each and every selector element had to be explicitly defined within the CSS. In addition, each and every CSS property had to be explicitly set back to its default. This led to a very large and brittle (~500 line!) CSS reset.
Modern CSS provides the solution
New(ish) to CSS is the all: revert
property / keyword combo. The all
value is shorthand to include all properties (except unicode-bidi
, direction
, and custom properties). The revert
keyword then reverts the cascaded value of these properties to the browser’s default value.
/**
* @file
* Reset HTML elements styles for the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper *:where(:not(svg, svg *, .ck-reset *, [data-drupal-ck-style-fence] *, .ui-resizable-handle)) {
all: revert;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
line-height: 1.4;
&:after,
&:before {
all: revert;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
}
}
This reset effectively prevents any styles from leaking in. But we also want to not affect certain elements (svg, .ck-reset, [data-drupal-ck-style-fence])
etc. To do this, we pair the :not()
selector with the :where()
selector.
The :not()
selector tells the browser to not apply these styles to its specified selectors. And putting it inside of :where()
removes the additional specificity that the :not()
selector adds.
The usage of :where()
allows these styles to be easily overridden with a simple #drupal-off-canvas-wrapper <selector>
selector pattern.
New custom property API for styling
As part of the refactor, we abstracted many of the colors, spacing, and typography to custom properties. This allows front-end developers to easily customize the look and feel of of the dialog with CSS similar to this:
#drupal-off-canvas-wrapper {
--off-canvas-background-color-light: #666;
--off-canvas-background-color-medium: #444;
--off-canvas-background-color-dark: #333;
--off-canvas-background-color: var(--off-canvas-background-color-medium);
--off-canvas-padding: 20px;
--off-canvas-text-color: #e5e5e5;
--off-canvas-link-color: #85bef4;
--off-canvas-border-color: #666;
--off-canvas-font-family: "Lucida Grande", "Lucida Sans Unicode", "liberation sans", sans-serif;
--off-canvas-vertical-spacing-unit: 8px;
--off-canvas-focus-outline-width: 2px;
--off-canvas-focus-outline-color: #fff;
}
Because we reset all styles cascading into the off-canvas, we had to restyle each component (e.g. details, button, dropbutton, table). We made each component style so each has its own custom properties that can easily be overridden. In turn, these custom properties default to the base off-canvas custom properties. So, changing the base custom properties will go a long way to changing all the styles!
#drupal-off-canvas-wrapper {
--off-canvas-dropbutton-height: 24px;
--off-canvas-dropbutton-primary-background-color: var(--off-canvas-button-background-color);
--off-canvas-dropbutton-secondary-background-color: var(--off-canvas-button-hover-background-color);
--off-canvas-dropbutton-text-color: var(--off-canvas-button-text-color); /* Minimum 4.5:1 contrast ratio against --off-canvas-dropbutton-primary-background-color and --off-canvas-dropbutton-secondary-background-color. */
--off-canvas-dropbutton-text-color-hover: var(--off-canvas-button-text-color-hover); /* Minimum 4.5:1 contrast ratio against --off-canvas-dropbutton-primary-background-color and --off-canvas-dropbutton-secondary-background-color. */
}
What do module/theme developers need to do?
If your module or theme doesn’t have any styles for the off-canvas, you need to do nothing. If you only have various fixes for the off-canvas, you can likely remove them (we removed the bespoke fixes for Claro, Olivero, and CKEditor5).
If you do have custom styling within the off-canvas dialog, you will likely need to change them. We had to do some slight refactoring of both Layout Builder and Workspaces module styles.
To use the new styles (Drupal 10 only), qualify all selectors within the #drupal-off-canvas-wrapper
selector. This new selector also includes the title bar as a descendent (previously it did not).
To affect the off-canvas dialog in only Drupal 8 and 9, you’ll want to use the #drupal-off-canvas:not(.drupal-off-canvas-reset)
selector. Make sure you include the :not(.drupal-off-canvas-reset)
part, otherwise it will affect all versions of Drupal (but not the title bar).
More information can be found within the official Drupal Core change record at https://www.drupal.org/node/3305664
Still work to do
Although the styles have now been refactored, there’s still work to be done on the off-canvas dialog. I’d personally love to give themes more control over the width, including taking into account the viewport.
Its JavaScript currently depends on the jQueryUI Dialog system, which has deep integrations into core. Hopefully someday in the near future, we can move to modern patterns such as the use of Web Components, etc.
Part of an overall effort
This merge request was overly large, with 83 commits over the course of 2 months. Special thanks to Lauri Eskola and Andy Blum for reviewing it and helping to push it forward.
This is only one issue that’s part of a larger effort to modernize Drupal’s front-end. These efforts include things like Responsive Views in core, use of CSS custom properties, refactoring of old CSS, new Twig features, and removing the various #DrupalWTFs when and where we can. I’m presenting on these at DrupalCon in Prague (in 3 weeks!!! 😱😱😱) on All the cool things you can do when you don’t support IE11 (and how we can use these in Drupal core) with Lullabot’s Andy Blum, and How Drupal 10 will make you fall in love with Drupal theming with Acquia’s Lauri Eskola.
If you’d like to help out with any of these efforts, please reach out! We’re in desperate need of more backend developers (who can create the APIs, tools, etc) as well as front-end devs.
Hey you! Leave a comment!6
Seriously... I really like it when people let me know their thoughts and that they've read this.
"Modern CSS provides the solution"
I'd like to mention, that this is a solution for the offcanvas core styles, while it destroys the styles of each. single. module. and. theme.
So forgive me.. if I really hate it, while I understand that theres no simple, nor a perfect solution to this topic. The old reset already felt shitty, but the new one forces us to use #id selectors and stuff, to override this reset, whats far from best practise in my small world.
Thats exactly the point. We don't want styles leaking in. if you want to style the off-canvas styles, you need to be very explicit about it. Prior to this reset, there were tens (if not hundreds) of usability and accessibility issues caused by styles leaking in.
Note that styling the 9.x era off-canvas also required IDs, !importants, and multiple nested CSS classes
As addition to my angry post: I really appreciate the work, but its SO frustrating how often I needed to fix our modules styles in context of the Core Offcanvas layer. Probably it was never a good idea to solve this without an Iframe => https://www.drupal.org/project/layout_builder_iframe_modal
Yeah, there's been lots of talk about doing this inside of an iframe, web components, etc. Either way, you'd still need to style it somehow.
If your module is in contrib, post a link and I'd be happy to give reviews, etc.
Yes sure, but I need to style it only once, without the need of generating duplicate styles (the regular styles/selectors + all styles/selectors prefixed with the offcanvas id).
However.. thanks for your response. I've not followed the discussions to this topic and I don't want to waste more of youre time. Fingers crossed.. that this is a long term solution and I don't need to touch this thing in the near future -_-
Perhaps this idea might help you @Thomas and is also helpful for other contrib cases?
https://www.drupal.org/project/drupal/issues/3395617
Looking forward to your feedback.