, , , , ,

A Million Little Pieces: Functional CSS in React

Reading Time: 4 minutes

All this isn’t to say that functional CSS is infallible. Rather, the purpose of this exercise is to explore a way we can lessen the pain of implementing a functional CSS system when used in a React environment. These pain points are as summarized in bold as follows:

Functional CSS relies on a large number of idiosyncratic class names whose documentation lives somewhere else (if at all). The correct application of which becomes esoteric knowledge.

// Via Tachyons
<article class="center mw5 mw6-ns hidden ba mv4">
<h1 class="f4 bg-near-black white mv0 pv2 ph3">Title of card</h1>
<div class="pa3 bt">
<p class="f6 f5-ns lh-copy measure mv0">
....

Elements with functional CSS classes grow to become ultimately unreadable. This adversely affects code clarity and the developer experience.

// Also via Tachyons
<a class="no-underline near-white bg-animate bg-near-black hover-bg-gray inline-flex items-center ma2 tc br2 pa2" href="https://github.com/" title="GitHub">

There is no accountability in writing class names. Developers can, and will, apply completely contradictory functional classes to elements with unpredictable results.

// This is all valid to write, but who knows what will render!
<div className="mg0 mg1 mg2 flex flex-row flex-column block inline float-left float-right hide show">
...

None of this is very React-y and the more you think in React, the stranger it will all feel. That’s because developers and designers who build in React, end up thinking in React and having to switch contexts to CSS and class names from a JS world will generate some cognitive friction.

This strange feeling has lead to a bunch of different ways to think about CSS in JavaScript and the evolution of solutions like Radium, Styled Components, Glamorous and many more. All of these abstractions bring CSS closer to the components they describe (a good thing™!), and allow users to define UI via CSS properties (also a good thing™!). However, none of these solutions give you the consistency, portability, and performance of functional CSS.

Further, even though it’s not vogue right now, there is a lot of benefit to keeping your source of truth for UI presentation in a segregated Sass/SCSS/CSS environment due the fact that it’s highly portable. CSS defined in a modular (BEM/OOCSS) and/or functional way will also be able to serve legacy and other non-react web platforms well into the foreseeable future.

Finally, maybe you’re like Shopify or one of the many other organizations building React UI component libraries. In which case, all UI component configuration is done via properties and CSS and class names are even further removed from the common workflow. At that point, making developers and designers implement simple layout and text decoration via CSS is just cruel.

Representing Functional CSS in React

You don’t have to throw away a perfectly good, exhaustive, and performant functional CSS toolset to make it in a React-forward world. A lot of these functional principles actually translate even better into a modular, JavaScript environment.

We are no longer going to apply classnames to elements, nor write CSS as style strings in React components, instead the concern of the developer will be configuring components that represent proxies to our well-defined functional system that is either included as a root import dependency in your sass-loader or imported directly by any of the components that utilize it. This approach can apply to any available functional library or one that you define. We can thank our Senior Front-end Developer James Panter for pioneering this approach for us here at Twitch.

These proxy components exist as metaphors described by:

  • Layout — A component that concerns itself primarily with properties of spacing, alignment, display, and position.
  • StyledLayout — A component that inherits the properties of Layout and also can apply typographic and visual styles like font-size, color, background-color, emphasis, etc.
  • Text — A component that concerns itself with the display of text styles, but can also render as any chose element type (span, p, h1-h6) where the other components necessarily render as divs.
  • InjectLayout — A utility that doesn’t render an element but rather injects class names into any arbitrary element or component that has a className property.

These are the metaphors that are working well for our team, but any set of component metaphors can be adopted as long as their configuration properties can proxy to existing functional classnames.

What This Means in Practice

All functional classes are scoped to 3 components. StyledLayout, Layout/InjectLayout, and Text can be used to compose almost all feature UI in concert with common components like Button, Tab, etc.

The only remaining CSS is used for widths/heights. This is a bit of a an exaggeration. There are also rules that define custom animations, and rules that have to exist so that modifier-based inheritance can be respected on state-change. That said, there is nothing stopping you from allowing users to directly set width/height on Layout level elements, a path we’ve not yet pursued.

// For example:
<StyledLayout display={Display.InlineFlex} alignItems={AlignItems.Center} padding={2} border>
<Text color={Color.Alt}>I'm a component composed entirely using functional CSS!</Text>
</StyledLayout>

CSS rules are exposed by a well-defined, documented API that can be accessed via intellisense/autocomplete. The implementation of these rules via configuration properties is enforced by TypeScript and keeps developers accountable while also holding their hand.

// text/component.tsx
// Font sizes are exposed on the Text component via an enum
export enum FontSize {
Size1 = 1,
Size2,
Size3,
Size4,
Size5,
Size6,
Size7,
Size8,
}
// ...
// The are mapped to corresponding functional classes.
const TEXT_FONT_SIZE_CLASSES = {
[FontSize.Size1]: 'font-size-1',
[FontSize.Size2]: 'font-size-2',
[FontSize.Size3]: 'font-size-3',
[FontSize.Size4]: 'font-size-4',
[FontSize.Size5]: 'font-size-5',
[FontSize.Size6]: 'font-size-6',
[FontSize.Size7]: 'font-size-7',
[FontSize.Size8]: 'font-size-8',
};
// ...
// And applied within the component
if (props.fontSize) {
classes[TEXT_FONT_SIZE_CLASSES[props.fontSize]] = true;
}
// ...
// my-feature/component.tsx
// The functional properties are then invoked in the feature component.
import { Text, FontSize } from 'text/component';
<Text fontSize={FontSize.Size4}>Hello World</Text>

Standardized APIs allow for complex composition of responsive rules:

<Layout
display={Display.Flex}
flexGrow={0}
flexWrap={FlexWrap.NoWrap}
margin={{ bottom: 2 }}
breakpointExtraSmall={{
alignSelf: AlignSelf.End,
flexOrder: 1,
}}
breakpointLarge={{
alignSelf: AlignSelf.Start,
flexOrder: 2,
}}
/>

The source of truth remains in the functional system. Your key tools for composing UI remain portable to other platforms and consistent across your application ecosystem.

You write more complex, composed UI in React while your CSS stays the same size. It works great at scale since adding more UI doesn’t increase the amount of CSS you add to the system.

Website: LINK

Facebook Comments

The Road to Victory in World of Tanks: TankBowl 2018

New Preview Alpha System Update – 1/19/18