The three statuses above and their possible values:

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 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

Challenges

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. (tick) 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

Challenges

Level of effort

Initial prototype – 1 - 2 weeks

Proposed decisions

6886fb53-17b2-4cd2-a34f-77bb567b847209abca86-ae7d-4894-98f5-7d222501c755DECIDEDWe will adopt a CSS in JS technology and incrementally replace our dependency on Bootstrap 4 in Paragon components (leaving css utils available)58df389f-8c67-4dd6-b663-2c05c4752092DECIDEDWe 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.c42d774a-76c7-4769-9886-00c0a1fb80adDECIDEDWe 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 https://github.com/edx/brand-edx.org/pull/34 .

Between the leading css-in-js implementations:

29325ff0-2544-44e5-af40-4a7055a497c128e4b0c4-11a0-4eb0-b3c5-77d4eec5a607DECIDEDUse @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:

<Box
  fontSize={4}
  fontWeight='bold'
  p={3}
  mb={[ 4, 5 ]}
  color='white'
  bg='primary'>
  Hello
</Box>

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

  <div
    sx={{
      fontWeight: 'bold',
      fontSize: 4, // picks up value from `theme.fontSizes[4]`
      color: 'primary', // picks up value from `theme.colors.primary`
    }}>
    Hello
  </div>

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?

24d3f0ed-42ac-4c0c-bccc-f7be8a5a459d1601e341-64d1-43c3-b7d7-f87a22a33fb3DECIDEDDefer 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.

6cd9eb46-ddea-4cb3-a645-5781a76e2294fa942165-3d86-4ed8-85c7-a4727bc1b69dDECIDEDAdopt 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.

https://github.com/edx/paragon/pull/702

https://github.com/edx/brand-edx.org/pull/34

https://github.com/edx/brand-openedx/pull/5

https://github.com/edx/paragon/pull/712

https://github.com/edx/brand-edx.org/pull/35

https://github.com/edx/brand-openedx/pull/6


Go, no go decision:

Initial adoption by MFEs – 2 - 4 weeks

David Joy (Deactivated) What do you think?

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.

Expected medium - long term outcomes

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.