Adopt CSS in JS for styling

APPROVED BY PWG FOR REVIEW WITH MGMT IMPLEMENTATION PENDING

The three statuses above and their possible values:

PROPOSAL STATUS

DESIGN STATUS

IMPLEMENTATION STATUS

PROPOSAL STATUS

DESIGN STATUS

IMPLEMENTATION STATUS

DRAFT

DESIGN PENDING

IMPLEMENTATION PENDING

READY FOR REVIEW

DESIGN PLANNED

IMPLEMENTATION PLANNED

APPROVED TO ADD

DESIGN IN PROGRESS

IMPLEMENTATION IN PROGRESS

DEFERRED

DESIGN COMPLETE

IMPLEMENTATION COMPLETE

NEEDS CHANGES

 

 

For more information see the https://openedx.atlassian.net/wiki/spaces/BPL/pages/1773502564/Component+Contribution+Process#Step-1-%E2%80%93-Start-a-component-proposal.

Background

 

Today’s Paragon implementation offers both a React component library and SASS framework in a complete foundation from which to build frontend React applications. Some key refreshers:

  • Paragon as a SASS framework extends Bootstrap 4 (“Bootstrap 4 + Extras”)

  • Paragon React components depend upon SASS styles to function.

  • Paragon offers utility classes and mixins (.mb-2, .bg-primary, @include media-breakpoint-up(md)) for applications to modify or create new styles in an application.

  • Theming of Paragon is done through overriding SASS variable defaults.

Paragon has been built as an extension of the Bootstrap SCSS framework since its inception.

This approach has served us well for many years, with many benefits and some challenges.

Benefits

  • The foundation of Paragon didn’t start from zero; We built on the shoulders of giants.

  • Styles are mobile responsive, cross-browser, and well tested.

  • Bootstrap documentation is a useful resource to lean on when Paragon documentation is incomplete or missing.

Challenges

  • React components are coupled with a SCSS framework.
    This makes upgrading Paragon risky since outputted CSS, by its nature, has global styling implications

    • For example: If we upgrade edx-platform from Bootstrap 4.0 (with its own small modifications) to Paragon SCSS framework, there is a high risk that we introduce visual bugs throughout the system. As a result we haven’t upgraded Paragon in the platform in years.

  • Paragon as a library is less approachable than it could be.
    As we expand the components offered in Paragon it becomes increasingly difficult to know what SCSS will apply to a given class name. In many cases digging into the Bootstrap 4 source code is necessary to understand what’s happening.

  • Sometimes Bootstrap gets in the way of styles we’d like to implement.
    The Button component styles for example are complicated due to extra functionality that Bootstrap offers, regarding gradients or auto choosing of text colors.

  • Bootstrap 5 likely contains breaking changes (including dropping IE11 support).
    If we upgrade to Bootstrap 5 it's likely to be considerable effort with little upside.

  • Theming is build-time only via SCSS
    It doesn’t scale well with more complex cases such as palette variations in a theme.

Key urgent problem: Upgrading Paragon in edx platform is extremely likely to introduce unexpected CSS interactions with existing styles. It will likely cause visual bugs that we miss and end up delivering to customers without heavy efforts to identify them beforehand.


Core issues with a SCSS approach to styling

  1. SCSS and CSS is global (until we get our shadow DOM game on).
    Today we mitigate this through the use of prefixes like .pgn__ or scoping #my-mfe .btn {...}. This has been the way, but over time this means:

    1. Upgrading foundational styles in large projects is high risk
      We don’t know what side-effects our new styles will have, visual breaking changes are silent.

    2. It becomes difficult to determine what styles apply to a given html element, making it time consuming to change existing styling
      If there are interactions between different styles its often very hard to wade through styles to make even simple updates (example: moving icons of the course card to a new line in a mobile view in edx-platform was very difficult work JJ tackled). In MFEs we mitigate this problem by keeping our applications small, but someday it will become a problem there too.

    3. SCSS is rarely deleted since it’s hard to know what impact deleting it will have.
      This exacerbates the above problems.

  2. Styles and markup are not colocated.
    This makes styling our applications harder than it needs to be. By adding matching class names in Javascript components and in SCSS definitions we:

    1. Invent names for things that already have a name or don’t need one:
      <MyHeader className=”my-header” />

    2. We create a ‘dual reusability’ problem (if someone knows a real term please teach me)
      Both our react component is reusable and our css class name is reusable. We can reuse the class name in many components or in raw html with no way to verify these relationships.

We need a solution that…

  1. Allows use to define styles for components without side effects.
    Our component style definitions should guarantee

  2. Keeps styling definitions close or connected to the components they apply to.
    We’ve improved this slightly in Paragon by moving SCSS into component directories, but it’s not a perfect solution.

  3. Let’s us grow out of Bootstrap.
    Out needs are growing beyond what Bootstrap offers. We need a solution that helps us scale our design system in a consistent way.

 


Proposal: Adopting CSS-in-JS

We have observed the power of mixing our javascript with our html – it’s time for CSS. By adopting CSS-in-JS we will address the above three needed solutions.

Example syntax of a Button.jsx react component:

const Button = styled.button` background: transparent; border-radius: 3px; border: 2px solid palevioletred; color: palevioletred; margin: 0 1em; padding: 0.25em 1em; `; const App = () => ( <Button /> );

This technology will dynamically generate a class name scoped only to this component. This technology is Gatsby compatible.

Libraries I propose we adopt:

These libraries are well established and trusted: https://styled-components.com/showcase. See them in use on http://zillow.com or https://www.spotify.com/us/ . On Github there are 33.3k stars for styled-components, and 6.7k stars for styled-system.

Benefits

  • Theming can happen at run time.

    • MFEs could swap themes without being rebuilt (valuable for Open edX operators).

    • Dark mode or user preferred tweaks become achievable for edx.org

  • Component styles are easily scoped to individual components without naming things unnecessarily.

  • We could upgrade Paragon in existing code bases that use Bootstrap with less effort and risk.

    • Because styles are scoped to individual components and don’t rely on the global style sheet they become portable and effectively have no css side-effects.

    • We will significantly reduce the effort and risk to upgrade Paragon in edx-platform.

  • Contributing to Paragon or MFEs will be easier to do (as far as styling goes):

    • Javascript, HTML, and styling all live together eliminating the need to understand a broader SCSS context to contribute well.

    • Contributors to MFEs will no longer need to determine an appropriate place to put SCSS.

    • If MFE owners adopt this technology for custom components over SCSS (not required) they will see similar benefits: reducing the risk of multiple teams introducing conflicting styling changes.

  • It becomes easier to remove styling code over time.

    • Since components would couple HTML, CSS, and Javascript, when a component is removed there would be no need to track down any related SASS.

Challenges

  • We will have two apis for making minor spacing or color customizations: Bootstrap CSS utilities (today), and Styled System prop apis.

    • Bootstrap CSS utility class names are used heavily throughout our MFEs, removing it entirely in the near or medium term is unlikely.

  • During conversion of Paragon components over to CSS in JS some will have the styled system prop apis available while others will not

    • This could cause confusion about when the new technology is available vs not

  • Contributors to Paragon will need to learn a new method of styling their components

    • We would make SCSS no longer be acceptable to add with new components.

  • There will be at least one breaking change in Paragon

    • This shouldn’t be too big of a problem, but all MFEs will need to add a <ParagonProvider theme={theme} /> components at the root of their React app and install a styled-components peer dependency. Component level changes are likely non-breaking.

Level of effort

Initial prototype – 1 - 2 weeks

  • Add the two CSS-in-JS libraries to Paragon

  • Create a ParagonProvider component for injecting a theme

  • Create theme.js and an edx.org theme that match our bootstrap theme variables

  • Attempt the conversion of 3 - 6 components to CSS-in-JS and remove their SCSS dependencies.

Proposed decisions

  1. We will adopt a CSS in JS technology and incrementally replace our dependency on Bootstrap 4 in Paragon components (leaving css utils available)
  2. We will develop plan to leverage this technology incrementally (and with only one breaking change) and in a reversible way. We will also outline the expected end-state of the adoption.
  3. We will adopt styled-components and styled-system as our CSS-in-JS and theming implementations.

Updates after prototype

After prototyping out several different solutions this PR is the desired path forward https://github.com/edx/paragon/pull/702 and brand updates https://github.com/edx/brand-openedx/pull/5 .

Between the leading css-in-js implementations:

  1. Use @emotion/react instead of styled-components

styled-components offers only the “styled api”

const Button = styled.button`color: red;`

Whereas the emotion family of packages offers both that api and several others. The API offered by @emotion/react uses a css prop:

<div css={css`color: red;`} />

The benefit of this api is that in the future any theming or style utilities that replace Bootstrap css utility classes can apply uniformly to any react element, Paragon or not.

Regarding theming packages

Both styled-components and emotion support theming, but are essentially unopinionated in how themes are constructed or consumed.

The theming/utility packages that build on styled-components or emotion respectively: Styled System and Theme UI, reflect the apis of the core packages they rely upon. I believe that using styled-system or any package that uses only the “styled api” would result in an inconsistent API for replacing Bootstrap css utilities and would cause significant developer pain. A themable component using Styled System may look like this where utilities are component props:

Theme UI, which relies on emotion seems like a better alternative, it’s api can apply to any component, whether Paragon or not:

This is encourage, but it requires cautious inspection. After working with it a little bit I’ve determined that we should use some of the underlying helper functions that theme-ui packages discretely and avoid wholesale adoption. Why?

  • styled-system and theme-ui are created by the same people, they abandoned styled-system abruptly to work on theme-ui. I fear the same could happen with theme-ui.

  • Theme ui supplies its own themable components and appears to be a batteries included system.

  • There are lingering open issues in github regarding how components with multiple axes of variance should be handled (e.g. size, colorScheme, variant of a button). It looks like something may land and be merged in the next six months, but in the meantime we would need to build our own workarounds anyway.

  • Theme ui creates yet another interface for developers to learn. I think adopting and getting used to working with emotion first will help the team understand the tooling rather than mixing things immediately.

  1. Defer building the CSS-in-JS version of CSS utilities later and iterate slowly on theming helpers

Emotion supports theming out of the box, but it’s somewhat verbose. Helper functions can significantly reduce the verbosity, but risk being too opinionated. We should move slowly and cautiously on this front. We can take our first steps without making this theming system decision right now.

  1. Adopt the theme-specification shape for theme.js files

Many theme-able libraries that utilize CSS-in-JS use this theme specification: https://styled-system.com/theme-specification/ . It outlines a well formed shape for themes. Even if we don’t use theme-ui or styled-system, this is a proven structure that we can safely adopt.

@Adam Butterworth (Deactivated) Will create step by step plan for what we do from here. Adam is out for 2 weeks starting 5/3. @Ben Warzeski (Deactivated) will develop or execute on the plan further if needed.

@Ben Warzeski (Deactivated) With this PR above Adam only transitioned one component in order to keep the PR comprehensible. Would you work on transitioning 2 - 5 more? Some are more difficult that others (Button I worked through, and was mildly happy with the result, but it forces us to make a lot of decisions on how we handle variants and colors in the theme so I didn’t want to start there)

@David Joy (Deactivated) Decide if it’s acceptable to merge these PRs. We will meet May 18, 2021.
We need to decide how the work of transitioning components to CSS-in-JS gets done. Should this be done by a contractor, internally, or swarm internally.

Go, no go decision:

  • Community Engineering team has veto power

  • Release a major version of Paragon (v.15.0.0) and minor version of brand-edx.org and brand-openedx

Initial adoption by MFEs – 2 - 4 weeks

@David Joy (Deactivated) What do you think?

  • Upgrade Paragon

  • Upgrade brand package brand-edx.org

  • Add ParagonProvider to React app

Incremental conversion of components to CSS-in-JS – Estimate TBD after prototype.

SWAG right now 26 engineer weeks (10 complete per week x 2). Paragon currently exports a total of 129 components. Also see component usages on the doc site.

asInput
ActionRow
Alert
Avatar
AvatarButton
Badge
Breadcrumb
Button
ButtonGroup
ButtonToolbar
Card
CardColumns
CardDeck
CardImg
CardGroup
CardGrid
Carousel
CarouselItem
CheckBox
CheckBoxGroup
CloseButton
Container
Col
Row
Collapse
Collapsible
Dropdown
DropdownButton
SplitButton
Fade
Fieldset
Form
CheckboxControl
SwitchControl
FormSwitchSet
FormControl
FormControlDecoratorGroup
FormControlFeedback
FormCheck
FormFile
FormRadio
FormRadioSet
FormRadioSetContext
FormGroup
FormLabel
FormText
InputGroup
Hyperlink
Icon
IconButton
Input
InputSelect
InputText
Image
Figure
ListBox
ListBoxOption
MailtoLink
Media
Modal
ModalCloseButton
FullscreenModal
MarketingModal
StandardModal
AlertModal
ModalLayer
ModalDialog
ModalPopup
ModalContext
Portal
PopperElement
Nav
NavDropdown
NavItem
NavLink
Navbar
NavbarBrand
Overlay
OverlayTrigger
Pagination
Popover
PopoverTitle
PopoverContent
ProgressBar
RadioButtonGroup
RadioButton
ExtraSmall
Small
Medium
Large
ExtraLarge
LargerThanExtraSmall
ResponsiveEmbed
SearchField
Sheet
Spinner
Stepper
StatefulButton
StatusAlert
Table
Tabs
Tab
TabContainer
TabContent
TabPane
TextArea
Toast
Tooltip
ValidationFormGroup
TransitionReplace
ValidationMessage
DataTable
TextFilter
CheckboxFilter
DropdownFilter
MultiSelectDropdownFilter
TableHeaderCell
TableCell
TableFilters
TableHeader
TableRow
TablePagination
DataTableContext
BulkActions
TableControlBar
TableFooter
CardView
ToggleButton
ToggleButtonGroup

For each component.

  • Remove SCSS import in index.scss

  • Add CSS-in-JS styles to component: Involves changes to the elements that are rendered. Example: <div className=”card” /> becomes const Card = styled.div

  • Leave the SCSS in the project with a comment that it is deprecated (maintain easy reversibility of the change)

  • Add any needed keys to the theme.js file.

Expected medium - long term outcomes

  • Bootstrap CSS utilities stay in our codebase forever.

    • Paragon continues to offer them as an export. New MFEs do not include the Paragon SASS.

    • Some engineers continue to use Bootstrap CSS utilities because they are used to them or they support a particular need.

  • Some deprecated components are never converted to CSS-in-JS.

    • They remain in the code base for over a year

    • We have avoided pushing teams off of deprecated components, in the past, we could continue this approach.

Adam B’s Belief I believe adopting CSS in JS will significantly improve the frontend developer experience both in MFEs and in edx-platform. Components in our React applications will be more modular, and engineers more free to create unique visual exceptions to code without worrying how they will impact the system as a whole.