r/reactjs•
kwhali

Are styling approaches heavily coupled to components?

Discussion

By that I mean how flexible can a component lib be that it doesn't get locked into a particular styling approach (eg emotion/styled-components styled API vs css modules vs object/prop styles, etc)?

Eg, if I were to have a component tree like this:

const NavList = () => (
  <List>
    <Dropdown label="Stuff">
      <Link href="/stuff-a">Stuff A</Link>
      <Link href="/stuff-b">Stuff B</Link>
      <Link href="/stuff-c">Stuff C</Link>
      <Link href="/stuff-42">Stuff 42</Link>
    </Dropdown>
    <Link external href="sub.example.com/">Somewhere</Link>
    <Link cta href="/do-it">Click Me</Link>
  </List>
)

// The 2 components below are are generic. If importing them to compose with the above component,
// then the List can be styled, but the Dropdown List would not inherit that..
// How to resolve that? Inject the List into into a DropdownFactory() that returns a Dropdown component?
const List = ({children}) => (
  <ul>
    {children.map((Item, index) => (
      <li key={index}>
        {Item}
      </li>
    ))}
  </ul>
)

const Dropdown = ({children, label}) => (
  <>
    <button>{label}</button>
    <List>{children}</List>
  </>
)

I realize the example is simple enough that you'd just opt for duplicating the component or adding the variant in the Dropdown component files exports. But you could envision deeper component trees with more complex components to compose new components.

Is it possible to define a headless / unstyled component library with all the functionality, and perhaps a default theme/styles to compliment but ultimately be more flexible / agnostic to the styling approach? Or is that not worth the effort?

Is there an existing technique / pattern to use here? Am I just defining components and composing them in the wrong way, or would my guess of using a HOC function to inject the components via an object (with defaults) and return a component be the right approach?

At least it seems like it could work well, just added boilerplate:

const defaultComponents = { List }
const withComponents = ({ List } = defaultComponents) =>
({children, label}) => (
  <>
    <button>{label}</button>
    <List>{children}</List>
  </>
)

// default component
export const Dropdown = withComponents()
export { withComponents as default } 

Then I could say use styled-components or whatever and create a variant like so:

import variantDropdown from '@components/Dropdown`
//...
// Create a variant with the List variant defined in this component
const Dropdown = variantDropdown({ List: StyledList })

Thoughts?

Posted Jul 1, 2021, 12:07 AM UTC

11
Loading comments…