-
Notifications
You must be signed in to change notification settings - Fork 51
[WIP-04] [Project Solar / Phase 1 / Showcase] Add support for theming and theme-switching to the showcase #3240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: project-solar/phase-1/HDS-5505_components/modes-css-compilation
Are you sure you want to change the base?
Conversation
The latest updates on your projects. Learn more about Vercel for GitHub.
|
b73dbf9
to
a4f41dd
Compare
a4f41dd
to
cc61972
Compare
519a6a6
to
3863dfa
Compare
cc61972
to
59418c1
Compare
59418c1
to
2f20cdc
Compare
2f20cdc
to
92910be
Compare
92910be
to
a1ff2f5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements comprehensive theming support for the HDS components library and showcase application. The implementation includes both infrastructure for theme management and UI controls for theme switching, enabling consumers to apply light/dark themes via multiple strategies (prefers-color-scheme, CSS selectors, or combined approaches).
Key changes:
- Added
hdsTheming
service andHdsThemeSwitcher
component to the HDS components library - Created
shwTheming
service extendinghdsTheming
with showcase-specific stylesheet management - Implemented showcase theming controls (
ShwThemeSwitcher
and sub-components) for testing different theming strategies - Added new showcase page at "Foundations > Theming" with demo components and frameless application example
Reviewed Changes
Copilot reviewed 56 out of 60 changed files in this pull request and generated 6 comments.
Show a summary per file
File | Description |
---|---|
packages/components/src/services/hds-theming.ts | Core theming service managing theme state, local storage, and CSS selector application |
packages/components/src/components/hds/theme-switcher/* | Temporary theme switcher component (dropdown-based) for light/dark/system selection |
showcase/app/services/shw-theming.ts | Showcase service extending HDS theming with stylesheet swapping logic |
showcase/app/components/shw/theme-switcher/* | Showcase theming controls with advanced options (modes, selectors, debugging) |
showcase/app/styles/showcase-theming/* | SCSS files defining light/dark color variables for showcase UI |
showcase/app/templates/page-foundations/theming/* | New theming foundation page with demo sections |
showcase/app/components/mock/app/* | Updated mock components to support theme switcher integration |
showcase/app/index.html | Added stylesheet links with swappable HDS components CSS |
showcase/app/components/page-foundations/theming/sub-sections/contexts.gts
Show resolved
Hide resolved
d3239c3
to
25c5e29
Compare
Co-authored-by: Copilot <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, think the approach taken here works really well. Theme testing through the showcase theme switcher and theming page was smooth, and straightforward.
} | ||
}; | ||
|
||
onApplyThemingPreferences = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Issue] Somewhere in this flow the _selectedLightTheme
and _selectedDarkTheme
updates are not getting captured / propagated to other areas.
If my theme, via the shw themeing select, is set to "carbon / dark (g100)", and I use the settings menu to set my light and dark themes to g10 and g90.
- My theme is visually updated to g90 as expected
- I use the select to change to any other themes, such as "carbon / light (g10)"
- My theme is incorrectly updated to the default light theme of g0
- The select options all show the default light and dark options of g0 / g100
- The settings menu still states my applied light and dark themes as g10 / g90 even though those aren't being applied.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah a bug introduced in one of the latest refactorings, I'm looking into it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, main part of the bug has been fixed in c3a9268.
The fact that the selected values in the popover are out of sync with the actual values in the service is a second bug that I'll look into now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Second part fixed in 7800278 (this was a bit trickier, I had to rethink some of the logic, due to the fact that the popover element is never removed from the DOM)
--token-color-surface-faint: #ffffff; | ||
} | ||
|
||
.hds-theme-light, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Question/Suggestion] Was this testing file from a previous implementation of the CSS files from the tokens? In the existing tokens PR there are no hds-{light/dark}-theme
and [data-hds-theme={light/dark}]
selectors.
I don't think these selectors would be feasible since a consumer could change what themes they want to use for the light and dark color schemes from the default choice of g0 and g100, but if these were included in our outputted css file like this they would always be tied to the default light and dark themes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, this is still part of the experimentation we're doing, around "how we want to expose theming to our consumers". One option I'm considering is to have simpler CSS selectors (hds-{light/dark}-theme
and [data-hds-theme={light/dark}]
) for the "standard" use cases (in the assumption that designers will want consumers to use only two themes, as general guidance) and then find a more complex alternative (but still pre-defined by us, eg. via the combined-strategy
) to let them offer to their consumers all the available themes (eg. the end-user may choose which light theme to use (between g0
and g10
) and which dark one to use (between g90
and g100
) for example in their settings, like you can do in GitHub for example.
In any case, something to discuss further
}, | ||
'Theming via CSS selectors': { | ||
'css-selectors|': 'HDS / Default', | ||
'css-selectors|light': `Carbon / Light (${this.gLight})`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'css-selectors|light': `Carbon / Light (${this.gLight})`, | |
'css-selectors|light|g0': `Carbon g0`, | |
'css-selectors|light|g10': `Carbon g10`, | |
'css-selectors|dark|g90': `Carbon g90`, | |
'css-selectors|dark|g10': `Carbon g100`, |
[Suggestion] For both the css selector and combined strategy options, it could be helpful for testing purposes to switch between all of the themes with the select, instead of what is currently selected as the light and dark themes. If we had a higher number of light and dark themes it would be quicker to look through each one just with the select instead of having to go update the advanced options each time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is my thinking: as a showcase user (developer/designer) I would use
- the "carbonized" pages (see [WIP-05] [Project Solar / Phase 1 / Showcase] Scaffolding of the showcase pages for the carbonization of the HDS components #3241) to see all at once all the possible variants, states and themes, for a component
- the normal pages for more standard/simple testing (where maybe I don't need to test all the themes, expecially when all the foundations have been set, so we can "trust" the colors/themes to just work)
} | ||
} | ||
|
||
@media (prefers-color-scheme: dark) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Note] I don't have a good solution to propose for this problem, but setting the color scheme dark showcase variables for the whole showcase can make reviewing default HDS theme components more challenging, when the dev has their system set to dark mode.
I have my system set to a dark color scheme, which means my whole showcase uses the dark showcase variables even when the default HDS theme is applied which does not have a dark version.
Because all of the HDS theme options in the theme selector don't trigger the light showcase variables, the only way for me to see the HDS components on their intended lighter background is by updating my system to be light.

Maybe one solution could be using actual HDS token values here instead of hardcoded ones. Then they would still be light when no theme is applied
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, that's also why I was considering having an explicit hds-theme-system
class or a data-hds-theme="system"
attribute applied to the root (#3240 (comment))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another consideration for using an explicit selector, is that we need to provide our consumers that want to use prefers-color-scheme
a way to turn on and off the "theming" functionality (opt-in, while they have already bumped the version and imported the "themed" CSS) e.g. to do the migration incrementally, while fixing their side of the theming.
This (which I've just realized) makes me strongly think that we don't have an alternative.
} | ||
|
||
// store the current theme and theming options in local storage (unless undefined) | ||
localStorage.setItem( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Suggestion] There could be use cases where a consumer wants to handle on their own how they keep track of a user's theme. They could want to handle it through their own cookies or storage or some other means. It would be useful to have an argument like hasLocalStorage
, where if false then nothing would get set from our service in local storage.
A consumer could then use the globalOnSetTheme
to listen for theme updates and handle them in their own way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we want to keep the globalOnSetTheme callback? which use cases do we foresee?
Because of my point above I think it would be useful to keep the callback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the current implementation, what they could do would be to use the setTheme()
call in the application controller, instead of the initializeTheme()
(see here) and set their own theme and options; this would write on localstorage, but they could simply ignore our values and use theirs way of storing this information (as you suggested, cookie for example).
The setTheme()
method has a callback, so in theory they may use that, instead of the global one, so we still have to find a good reason to keep it alive :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would using globalOnSetTheme
be easier or more straightforward for them to use vs. setTheme()
in any way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we want to add hds-theme-system or data-hds-theme="system" when the user-selected option is system?
Personally I don't think this is needed. We could say that if you are using the system theme you are then relying on the prefer-color-scheme selectors and shouldn't be trying to control that with some other selector.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tend to agree with this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment above (I am not sure it would work, but that's why I am still keeping it open as a possibility)
…he states were potentially out of sync
this commit will be removed once we pull in `main` (after the corresponding PR is merged)
} | ||
|
||
enum HdsModesBaseValues { | ||
Hds = 'hds', // TODO understand if it should be `default` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If consumers don't do anything, the "Hds" theme is the one they should get so that would be a "default". Is that what you mean?
|
||
enum HdsModesDarkValues { | ||
CdsG90 = 'cds-g90', | ||
CdsG100 = 'cds-g100', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still don't really understand the purpose of having multiple Light & Dark value options.
export default class HdsThemingService extends Service { | ||
@tracked _isInitialized: boolean = false; | ||
@tracked _currentTheme: HdsThemes | undefined = undefined; | ||
@tracked _currentMode: HdsModes = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be clearer to include "undefined" for the type here (vs. including it in the type definition above) the same way it's defined for _currentTheme
or is that a dumb question? (Or change the type definition for HdsThemes
?)
(i.e. use HdsModes | undefined = undefined;
)
'assets/styles/@hashicorp/design-system-components-theming-with-combined-strategies.css'; | ||
break; | ||
default: | ||
// this is the standard CSS for HDS components, without any theming |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// this is the standard CSS for HDS components, without any theming | |
// this is the "standard" CSS for HDS components, without any theming |
(just to make it stand out a bit more clearly)
Note
This PR is quite large, we may consider splitting it up in multiple PRs
📌 Summary
This PR contains multiple pieces of the "theming" puzzle:
hdsTheming
service and a (temporary)HdsThemeSwitcher
componentshwTheming
service and set of showcase controls to switch theming (and theming options) plus the CSS file linked in the applicationThis is based on top of #3259
🛠️ Detailed description
The first commits are a direct porting of previous showcase theming explorations (squashed):
After the initial cherry picking of code, there's been a lot of back and forth / refactorings, with different attempts in reaching an implementation that could (hopefully) withstand how different teams may want to adopt theming in their codebases. For this reason, the bulk of the work has been around finding the right "location" for the different pieces of logic, the different concerns: what goes into the HDS theming service vs the SHW theming service; how theming preferences are expressed in the UI and controlled by the user; how consumers decisions are reflected and stored and passed over between HDS/SHW services and components; how a user would use the theming controls (including a showcase user/developer/designer) and how a consumer would allow that user to interact with the application.
It's impossible to describe in detail all the code changes and implementations introduced in this PR, you have to look at diff in code, the showcase application, the "how to review" and "how to test" sections below, and try to get a general picture of what went into this PR.
👉 👉 👉 Preview: theming page
🔎 How to review
This is a quite large PR (we may consider splitting it). I would suggest to look at a piece of code/logic (eg. HDS theming service), see how this interacts with the rest of the code (in HDS and/or in the showcase), and in parallel I would try to see in the showcase application how this logic is reflected in the UI, and try to interact with it to see what happens.
The "logical" order in which I would look at things is this:
hdsTheming
service andHdsThemeSwitcher
component underpackages/components
shwTheming
service undershowCase
and see how it's an extension of thehdsTheming
(something we may undo, if we remove theglobalOnSetTheme
callback, see below)ShwThemeSwitcher
controls (component and sub-components) and see how they're using internally both thehdsTheming
andshwTheming
services, how the "current" theming options are reflected in the services and in the controls values/states, what happens when a control value is updated/applied, etcfoundations/theming
showcase page, and its sectionsHdsThemSwitcher
componentpublic/assets/styles/@hashicorp/
folder, in particular the CSS selectors, and see if they make sense🛠️ How to test
Launch the showcase application locally, then visit the "Foundations > Theming" page. Here play with the controls in the top right of the page, and see how changing them impacts the page:
If you want to double check how the tokens and their values are applied depending on the chosen theme/options, you can update the colors in the temporary/testing CSS files under the
public/assets/styles/@hashicorp/
folder (see the CSS variables under theadded these to test theming in the showcase
comment)💬 To discuss/decide
$modes
support to tokens pipeline #3239, where we set/write the CSS selectors in the actual themed token files)hds-theme-system
ordata-hds-theme="system"
when the user-selected option issystem
?:not()
selectors (see below)_currentStylesheet
be stored in local storage or in URL as query param (same for the othershw
preferences)?globalOnSetTheme
callback? which use cases do we foresee?🗒️ TODOs
hdsTheming
serviceHdsTheming
component:root:not([class*="hds-theme-"],[data-hds-theme])
(it may not work, if we usehds-theme-system
ordata-hds-theme="system"
)public/assets/styles/@hashicorp/
folder and replace them with a proper implementation based on the Ember build process📸 Screenshots
Screen.Recording.2025-10-21.at.15.31.39.mov
🔗 External links
Jira tickets:
👀 Component checklist
A changelog entry was added via Changesets if needed (see templates here)💬 Please consider using conventional comments when reviewing this PR.
📋 PCI review checklist
Examples of changes to controls include access controls, encryption, logging, etc.
Examples include changes to operating systems, ports, protocols, services, cryptography-related components, PII processing code, etc.