Page MenuHomePhabricator

[EPIC] Add CSS-only components
Closed, ResolvedPublic

Description

What is it?

CSS-only components would be a group of Codex components that are implemented solely by creating standard, shared styles for each component, meaning JavaScript would not be required to use them. For example, to use a CSS-only Message component, a developer would either write or generate the markup for the message, then apply the shared styles to their markup, resulting in a no-JS message that has visual consistency with the Vue version of the Message component.

Currently, nearly all Codex components are implemented as Vue.js components, meaning they require JavaScript running on the client to work. Eventually, we hope to have a solution for rendering Vue components on the server (server-side rendering, or SSR), but this will take time, and there still may be use cases for no-JS versions of Codex components even if we have full SSR.

What does it block?

Some WMF teams may be able to start using Codex (and, therefore, the current and canonical design system) if they have access to these components while they're waiting on SSR. Existing uses of deprecated libraries (like mediawiki.ui) cannot be removed until there's something else to migrate to.

Impact

End users:

  • Users without JavaScript can see and use UIs that have a consistent design that matches our canonical design system, across wikis
  • Users with slow connections can see and start to use visually consistent UI components while they await the fully interactive JS version of the UI to load
  • All users benefit from simple UIs that can be rendered on the server, which load quickly and provide a consistent experience
  • Non-WMF wikis have a way to provide consistent server-rendered UIs without having to set up SSR
  • Easily available API could mean misuse on-wiki, e.g. applying interface styles to content, or more potential for breaking visual changes due to high usage of CSS classes

Engineers:

  • While they await an SSR solution, or in situations where SSR cannot be used, engineers can use CSS-only Codex components to style server-rendered elements, enabling them to use the current design system and reducing the need for custom code.
  • Engineers with no JavaScript or Vue experience can build UIs with Codex components
  • We can eventually deprecate old, inconsistent parts of our systems in favor of a single, centralized set of styles, leading to lower maintenance costs, less confusion, and easier onboarding of new developers
  • Abstracting styles out of Vue components will mean having a set of implementation-agnostic base styles that could be applied to a new implementation in the future (e.g. React or a new framework)
  • Development and maintenance costs: building this set of components will require time from DST and other teams to develop and test

Designers:

  • Since styles are shared between CSS-only and Vue components in Codex, designers will have less to review when adding or changing components
  • Once we deprecate old systems, designers will have fewer things to keep track of and work on, leading to greater velocity on the current system
  • Designers who know HTML and CSS can use CSS-only components for rapid prototyping or to make code changes for production UIs

Proposed implementation strategy

Proposal

  1. Use cdx- classes for styling ("Bootstrap-style" approach). We are already writing BEM-style CSS inside our Vue components, so we don't need to make any changes here.
  2. Continue writing styles within Vue single-file components (SFCs). Where necessary, components will make distinctions between two sets of styles. The first is styles that only appear in the CSS-only version, and styles that only appear in the Vue version (both SSR-ed and mounted). We can distinguish these styles by adding -vue to the relevant classes to target Vue-only styles. The second is styles that only appear in a no-JS environment, and styles that only appear in a JS-enabled environment. We can distinguish these styles by adding a --js-on or --mounted class suffix when the component is mounted. Finally, we will add a ref to most components that will be set to true on (or right before) mount, via a composition function, which can be used to conditionally show different markup depending on whether the SSR-ed component has been hydrated or not (this last part could be done in the future during SSR implementation).
  3. Define a series of new Codex sub-packages which are intended for use within MediaWiki. Each new package will group together a set of related components (maybe corresponding to the component groups we were considering adding to the documentation site). This will work similar to how we are handling codex-search currently. Each new package will get built in Vite’s library-mode, so a single JS and CSS file gets built with only the data for the relevant subset of components. An example of this can be seen in this patch, which creates a demo codex-buttons package: https://gerrit.wikimedia.org/r/c/design/codex/+/865840
  4. Each Codex sub-package gets its own ResourceLoader module containing only its CSS and JS files. Users only need to load the packages corresponding to the components they need in their feature.
  5. Load CSS and JS separately: There are two ways we can do this.
    • We could update ResourceLoader so that a user can get only the styles, or only the scripts, or both, when using a module that contains both CSS and JS. Currently this is only possible through an annoying work-around.
    • Alternatively, we will have to do this work manually. Each Codex sub-package would need to have two modules in MW – one containing only the CSS, i.e. codex-buttons-styles, and one containing only the JS files, i.e. codex-buttons. The latter package would depend on the former, but the former can be used without the latter.
  6. Interaction with SSR: In the future, we expect that a more robust SSR system will be available in at least some situations. SSR-based features would basically create their own bundle instead of relying on the pre-split ones that we’d be providing here. Such builds will depend on the full Codex package (which contains all CSS and JS code). The SSR tooling will be able to tree-shake out all unnecessary code for these features, so the regular Codex modules will not be needed at all.

Acceptance criteria

  • Most Codex components should have a CSS implementation provided alongside the Vue implementation (unless there is a good reason not to provide one – Dialog will likely be JS only for example)
  • CSS and Vue versions of the same component would share styles and class names; the Vue version of the component would expect for the appropriate CSS styles to already be present on the page
  • Visual parity between CSS / JS implementations of a component should be the goal in most cases
  • Seamless progressive enhancement (where CSS components get replaced with JS-based ones shortly after the page loads) is another goal; Ideally user would not notice until they interact
  • CSS and JS versions of the same component should be able to coexist on the same page without any issues;
  • Component styles should never be double-loaded; If a CSS Button is being used on a page which is progressively enhanced, the JS code should not duplicate the styles which are already present
  • Users should not need to load all of Codex when they only need one or two components. This applies equally for CSS and JS use-cases.
  • It should be possible to load CSS components independently of their JS counterparts
  • JS component module(s) should have the CSS equivalent as a dependency; this ensures that in non-progressive-enhancement scenarios (i.e. feature requires JS to work), developers who load the Vue components will also load all relevant styles

Future work

In the system described here, users of CSS-only components would still be responsible for authoring the proper markup for their UI. This will likely be somewhat tedious for all but the simplest features. We could make this experience a lot better by providing one or both of the following:

  • Better templating language support on the server; it would be great if template authors could just include a {% cdx-button %} partial that can take arguments corresponding to component properties
  • Alternatively, a PHP interface could be provided similar to how OOUI-PHP works currently

Feasibility of this approach

Aside from the potential (small-ish) changes to ResourceLoader (see point 4 above), we can do all of this now. And actually most of the styles we need already exist in the Vue components themselves. Most of the work would just be a matter of reorganizing some of our current code so that it can be used in multiple ways going forward.

Risks/Trade-offs

  • This implementation strategy will allow us to keep writing our component styles within the Vue SFCs, which is nice from a familiarity and developer productivity approach.
  • We would need to create some additional packages inside the Codex repo, but this will mostly be a one-time task. As we add new components to Codex we'll need to include them in one of the existing sub-packages or (more rarely) create a new one. But little additional code is required here and once a sub-package has been set up it will rarely change. These will be included in each release just like codex-search is (though we may not need to publish them on NPM since they are intended only for use in MW). See https://gerrit.wikimedia.org/r/c/design/codex/+/866509/ for an example of what this would look like.
  • CSS-only implementations of components will need to be tested; VueTest is a good place to do this (maybe a new sub-page of that extension can be created for the CSS-only components)
  • We'll need to create several additional ResourceLoader modules (especially if we cannot come up with a way to get just the CSS or JS from a module that contains both). But like in the case of the additional Codex packages, most of the work here is a one-time effort.
  • We might want to include some easily copy-pastable markup snippets for users in the absence of a good way to package up CSS-only components in mustache templates or PHP.

Overall we think this approach is pretty low-risk, and doesn't rely on any speculative or complex new technical work.


Phases of work and owners

  1. Strategy
    • Owner: Anne/Eric.
    • To do:
      • Choose an implementation path (done, proposed above)
      • Decide on an MVP implementation and set of components (done, proposed above and in T325105 )
      • Determine how to document components (done, see T325105)
      • Determine testing strategy
      • Get feedback from major stakeholders on implementation plan
      • Consider documenting any standards that come out of this research, like prioritizing native browser elements/styles when possible, or building the CSS-only version of a new component first before the Vue verison
  2. Build CSS-only versions of components (we are here).
    • Owner: Anne.
    • To do:
      • Build MVP set of components. Start with high priority, then move through medium and low (covered by T325105)
      • Work with QTE to test components as they're developed
      • Work with other teams to test/validate specific components
  3. Enable use of components within MediaWIki.
    • Owner: Eric.
    • Validate proposal with performance team
    • Define sub-packages and create RL modules
    • Enable loading of CSS and JS separately
  4. Application of CSS-only components.
    • Owner: DST + Web + Others?
    • To do:
      • Strategize with teams and volunteers on how they can use CSS-only components and migrate from deprecated libraries
      • Document a migration plan for legacy libraries?

Cross-team dependencies

  • Web, Growth: These teams will be a major consumers of these components and should be involved in strategy and testing at least.
  • Performance: We may need to get the Performance team's feedback on any changes that may impact performance.

Related Objects

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes

From Sergio:

CSS-only components would be nice already for interface shown "while waiting for JS loading", ie: skeletons (https://phabricator.wikimedia.org/T311874)

Loading indicators in general would be nice to have (e.g. ProgressIndicator, which we have yet to implement at all)

Volker_E updated the task description. (Show Details)
Volker_E updated the task description. (Show Details)

Per today's DST Task Planning meeting, we're going to update this task to include some of the information from the new technical epic template. This will help with the longer-term planning for how we want this work to proceed.

  • Users with slow connections can see and start to use visually consistent UI components while they await the fully interactive JS version of the UI to load

This implies that there would be a way to hydrate CSS-only components, or that they would they look so similar to their JS counterparts that we could replace a CSS-only component with a JS-based one without the user noticing. I think that will probably be possible for components that are simple and/or components where we carefully keep the CSS-only HTML and the Vue HTML in sync, but we should think through the technical feasibility of this before we assume that we can do it.

  1. Do the short and long term benefits outweigh the development costs and justify the changes needed to implement this? Will these components be actively used once we have SSR in place?

My current working assumption is that CSS-only versions of basic components (e.g. buttons, icons) would continue to be used, because SSR may have issues with performance or third-party wiki support that could prevent it from being used for those kinds of cases. But if those issues were to be solved, there's an argument that using SSR for everything would be better, because then we don't have to have duplicate implementations of these components.

This implies that there would be a way to hydrate CSS-only components, or that they would they look so similar to their JS counterparts that we could replace a CSS-only component with a JS-based one without the user noticing. I think that will probably be possible for components that are simple and/or components where we carefully keep the CSS-only HTML and the Vue HTML in sync, but we should think through the technical feasibility of this before we assume that we can do it.

Agreed; we should think through the existing and planned components to see if there are any where we couldn't commit to this. That said, I think it seems quite achievable for most components (I'm not sure about something like Select). We had to do this for MediaSearch, and it was possible because most of the interactivity features were additive (adding a dismiss button to a Message, or a menu to a SearchInput) rather than disruptive to the server-rendered version.

This implies that there would be a way to hydrate CSS-only components, or that they would they look so similar to their JS counterparts that we could replace a CSS-only component with a JS-based one without the user noticing. I think that will probably be possible for components that are simple and/or components where we carefully keep the CSS-only HTML and the Vue HTML in sync, but we should think through the technical feasibility of this before we assume that we can do it.

Agreed; we should think through the existing and planned components to see if there are any where we couldn't commit to this. That said, I think it seems quite achievable for most components (I'm not sure about something like Select). We had to do this for MediaSearch, and it was possible because most of the interactivity features were additive (adding a dismiss button to a Message, or a menu to a SearchInput) rather than disruptive to the server-rendered version.

I think that for the majority of Codex components, complete visual parity between JS and CSS versions should be the goal. As @AnneT mentioned there will probably be a few places where this not possible for some reason, but we should make this part of the acceptance criteria for most of our work here.

Regardless of whether a page is progressively enhanced (CSS components -> Vue components) or rendered via a full SSR process, ideally the user never notices that things have changed until they start interacting.

I'd like to propose a set of acceptance criteria for the task under consideration. I'd love to hear folks thoughts around these (are they doable, are they correct, is anything missing, etc).

CSS Components: Draft Acceptance Criteria
  • Most Codex components should have a CSS implementation provided alongside the Vue implementation (unless there is a good reason not to provide one – Dialog will likely be JS only for example)
  • CSS and Vue versions of the same component (ex. Message) would share styles and class names; the Vue version of the component would expect for the appropriate CSS styles to already be present on the page
    • Components would still include their own CSS in cases where styles are only relevant for JS-enabled interactions. For example styling for a user-dismissible variant of the Message component would live inside the Vue SFC. Styles for success/error/warning Message variants would live in CSS.
  • Visual parity between CSS / JS implementations of a component should be the goal in most cases
  • Seamless progressive enhancement (where CSS components get replaced with JS-based ones shortly after the page loads) is another goal; Ideally user would not notice until they interact
  • CSS and JS versions of the same component should be able to coexist on the same page without any issues;
    • there may be cases where two different features appear on a page; both need a Button component, but only one feature uses JS. User should not see any difference.
  • Component styles should never be double-loaded; If a CSS Button is being used on a page which is progressively enhanced, the JS code should not duplicate the styles which are already present
  • Users should not need to load all of Codex when they only need one or two components. This applies equally for CSS and JS use-cases.
    • The exact level of granularity we can support is still an open question:
      • Can we allow users to specify the exact components that they need in JSON or PHP, like how we handle Icons?
      • Should we instead offer some pre-split bundles of a few related components similar to how OOUI operates? If we do this, we should offer the same “splits” for the CSS and JS components
  • It should be possible to load CSS components independently of their JS counterparts
  • JS component module(s) should have the CSS equivalent as a dependency; this ensures that in non-progressive-enhancement scenarios (i.e. feature requires JS to work), developers who load the Vue components will also load all relevant styles
  • Users should not need to load all of Codex when they only need one or two components. This applies equally for CSS and JS use-cases.
    • The exact level of granularity we can support is still an open question:
      • Can we allow users to specify the exact components that they need in JSON or PHP, like how we handle Icons?
      • Should we instead offer some pre-split bundles of a few related components similar to how OOUI operates? If we do this, we should offer the same “splits” for the CSS and JS components

While I agree that this is a worthwhile goal, I don't quite agree that it should be an acceptance criterion for this epic. This is an issue that already exists in the JS-only world, and CSS-only components are not affected by it (much) worse than JS ones, so we should not condition adding CSS-only components on being able to solve this issue.

While I agree that this is a worthwhile goal, I don't quite agree that it should be an acceptance criterion for this epic. This is an issue that already exists in the JS-only world, and CSS-only components are not affected by it (much) worse than JS ones, so we should not condition adding CSS-only components on being able to solve this issue.

I think the way we solve this problem might have implications for how we proceed, so I think we ought to keep it in mind.

egardner changed the task status from Open to In Progress.Dec 8 2022, 11:13 PM
egardner triaged this task as High priority.

I like the idea!

One thing I want to note is that if you make CSS-only components available, they definitely will get used in the wikitext of pages. You mention "misuse on-wiki, e.g. applying interface styles to content" as a possible disadvantage, but I don't think it's always a bad thing – I agree it'd be a misuse in articles, but there are other use cases where the wiki pages are the interface we've never built into the software, e.g. https://en.wikipedia.org/wiki/Help:Introduction or https://pl.wikipedia.org/wiki/Pomoc:Pierwsze_kroki (currently using the twice-deprecated "mw-ui-…" classes – those were pretty much the original CSS-only components).

With that in mind:

  • You might have a hard time making any breaking changes once the feature is released
  • You might want to consider adding some extra support for this in wikitext, e.g. a parser function that would load the bundle CSS
  • You might want to consider adding some extra support for this in wikitext, e.g. a parser function that would load the bundle CSS

(Like T101666: Create parser tag(s) that render OOUI PHP widgets I assume. ;)

(I was hoping it'd be something simpler than that, since here you wouldn't need to render the component, just cause its styles to be loaded. Basically, I would like something to replace this hack for mediawiki.ui styles: https://gerrit.wikimedia.org/g/mediawiki/core/+/630925a09d40ee542887e08da638f31aae829074/includes/skins/Skin.php#404 but it seems nicer to do it in a more explicit way with a parser function/tag.)

  • You might want to consider adding some extra support for this in wikitext, e.g. a parser function that would load the bundle CSS

T241524: Parser function for loading gadgets is somewhat similar; the concerns brought up there might apply.

NBaca-WMF added a subscriber: NBaca-WMF.

Mentioning https://phabricator.wikimedia.org/T321424 here to conceptually link this work with potential upcoming work for Web - would be interesting to consider how / when that work could be phased relative to this, we should discuss this going into next quarter's planning

The big benefit of this work would presumably be icons and buttons in the web team, but I'd like to see the CSS mixin approach applied to cdx-message before approaching this use case to confirm its viable - https://github.com/wikimedia/mediawiki/blob/master/resources/src/mediawiki.skinning/messageBoxes.less is our current legacy CSS for that (which is supposed to be synced with Codex) and the HTML is generated in https://github.com/wikimedia/mediawiki/blob/b8abc26010416ecc4b7d5576ecc50c37c3eb0519/includes/Html.php#L744
Is this something we can try first ?

The big benefit of this work would presumably be icons and buttons in the web team, but I'd like to see the CSS mixin approach applied to cdx-message before approaching this use case to confirm its viable - https://github.com/wikimedia/mediawiki/blob/master/resources/src/mediawiki.skinning/messageBoxes.less is our current legacy CSS for that (which is supposed to be synced with Codex) and the HTML is generated in https://github.com/wikimedia/mediawiki/blob/b8abc26010416ecc4b7d5576ecc50c37c3eb0519/includes/Html.php#L744
Is this something we can try first ?

@Jdlrobson I've created a new sub-task to track this work over at T326587: Replace core Message Box styles with Codex equivalents – feel free to update the description there if anything is missing. I agree that this is a good concrete task we can try to accomplish in the near future to help inform how we proceed here.

Each Codex sub-package gets its own ResourceLoader module containing only its CSS and JS files. Users only need to load the packages corresponding to the components they need in their feature.

How many additional ResourceLoader modules would you create? My understanding is that we should try to limit the number of new RL modules (see e.g. T202154 for past analysis and subtasks relating to reducing module count). Maybe the combined CSS size is lightweight enough to include in a single RL module?

How many additional ResourceLoader modules would you create? My understanding is that we should try to limit the number of new RL modules (see e.g. T202154 for past analysis and subtasks relating to reducing module count). Maybe the combined CSS size is lightweight enough to include in a single RL module?

We'd probably have a fairly small number of component groups (I have a hard time imagining more than 5-6 total) – in part because I want to avoid complex dependency relationships; similar to OOUI I imagine we'd have some kind of codex-core grouping and then other smaller ones that depend on that.

However, each component group would need to have 2 separate modules – one for styles and one for the JS (which would depend on the styles). This would make it easy for a team to just use the CSS components, or to load all the CSS and JS at once. I'm not anticipating single-component modules.

I'm hoping that this is a small enough impact in the grand scheme of things to not cause an issue; there is a trade-off here between how granular we make each module (meaning less danger of shipping unnecessary code to the user) vs how many additional modules we need to define in RL. We are open to suggestions on best ways to handle this.


On another note, I'd like to ask any folks who are considering using CSS components (@kostajh? @Jdlrobson?) what your feelings are in terms of generating the necessary markup for these styles. Right now we don't have a great way to provide that to consumers except for some copy/pastable snippets of code on our documentation site. @AnneT has been trying to simplify markup and CSS classes where possible to make this less painful, but the experience still leaves something to be desired.

I would love to provide a set of template partials for these components so that teams writing JS-free UIs could just do something like {{> Button type="primary" action="progressive" }}. Unfortunately, the basic Mustache library we use allows partials but they don't take parameters, which gets in the way of any "componentization". If we were able to rely on a more full-featured templating library (there are many) this would not be a problem – in that case perhaps DST could write a set of Codex-compatible templates which teams could use as a follow-up task here.

Adding @alexhollender_WMF as a designer who regularly creates web prototypes, in case you have any feedback to add about potential usage by a designer end-user.

Wrt use of these components in wiki content: IMO it's neither very useful (there are only a few components for which this would make sense, like link buttons, and those are easy enough to manually implement with different CSS) nor particularly problematic (wikitext has its own templating system so changes would be manageable). Making Codex easy to use in gadgets would be very important for a unified look-and-feel but that's a whole different issue since gadgets can make use of ResourceLoader and don't have much use for CSS-only components. (Launch buttons, maybe.)

That said, I think there are two approaches for using them in wikitext: either via copy-pasting the HTML snippet (and then we'd have to use some classname regex to detect that and make sure the styles are loaded) or via dedicated parser functions ({{#codex-button}} etc., which should very simple once there is some equivalent functionality for MediaWiki PHP code). The benefit of copy-pasting is that it relies on the normal sanitization process so there is no need to be careful about XSS risks; the drawback is that not all HTML is allowed in wikitext (most notably, <a> isn't - you can use wikitext links instead but they can't have attributes), plus it's a little more effort for editors.

I would love to provide a set of template partials for these components so that teams writing JS-free UIs could just do something like {{> Button type="primary" action="progressive" }}. Unfortunately, the basic Mustache library we use allows partials but they don't take parameters, which gets in the way of any "componentization". If we were able to rely on a more full-featured templating library (there are many) this would not be a problem – in that case perhaps DST could write a set of Codex-compatible templates which teams could use as a follow-up task here.

A template would only be useful for developers using the same template language, right? So a new template language would only provide benefits if everyone else switched.

Something similar to the current Html::linkButton() etc. methods would be nice, since manual construction of HTML is still pretty widespread. It would also make it fairly simple to create bindings for any template language.

A template would only be useful for developers using the same template language, right? So a new template language would only provide benefits if everyone else switched.

Something similar to the current Html::linkButton() etc. methods would be nice, since manual construction of HTML is still pretty widespread. It would also make it fairly simple to create bindings for any template language.

I assumed a templating language would be more user-friendly and desirable (that's the approach I am more familiar with), but we could consider introducing some kind of Codex PHP class into MediaWiki (Codex::Button(), Codex::Select(), etc). You'd probably want a call signature where the first param is an object containing the various props (maybe plain HTML attributes would also be included under an $attrs key or something), and the last param is an array of content elements. For certain components this could include other tags/components with their own nested children; a markup string would be returned.

If this is the kind of feature folks think would be useful let me know. Seems very doable but I'd want to partner with a more experienced PHP dev if we ended up needing to introduce such a feature.

Adding @alexhollender_WMF as a designer who regularly creates web prototypes, in case you have any feedback to add about potential usage by a designer end-user.

I'd love to try using some of these codex-lite components in a prototype whenever they're ready to test out

I assumed a templating language would be more user-friendly and desirable (that's the approach I am more familiar with), but we could consider introducing some kind of Codex PHP class into MediaWiki (Codex::Button(), Codex::Select(), etc). You'd probably want a call signature where the first param is an object containing the various props (maybe plain HTML attributes would also be included under an $attrs key or something), and the last param is an array of content elements. For certain components this could include other tags/components with their own nested children; a markup string would be returned.

I'd think of those as complementary options; it really depends on the use cases and conventions of the client codebase. If you construct the HTML inside a PHP class (which is easier e.g. when the HTML is highly dynamic, or you want to allow extensions to hook in and alter the HTML), it's more convenient to have a PHP method for each Codex widget like we have for OOUI. If you are using a templating language, it's more convenient to have a template.

But mainly the point I wanted to make is that a template is only convenient if you are using the same templating language. So if Codex used a different language, everyone else would also have to switch to that language to reap the user-friendliness benefits. (This might be moot, based on what @Jdlrobson said at the meeting, but it's worth being clear about.)

The Design Systems team is removing the majority of our "meta" tasks like this one. Please follow this work in T325105 instead.