💅 Styled-Components: Extending Children
Tags: css, react, javascript, webdev
Published: 2020-03-02 12:39:06

Even when `styled-components` often export different variations of the component it can still be useful to control the styles from the parent. For example, a parent might want to adjust the position of the child.

```jsx
import { Button } from '../components/button'

const Modal = () => (
  <aside>
    {/* How do we customize this button? */}
    <Button>Close</Button>
  </aside>
)
```

While you could export version with different positioning styles or that takes a position prop, it has more to do with the parent and visual context than the child itself and could be cumbersome to export a different version or create a property for every possible visual context.

As you can see, this gets out of hand very quickly:

```jsx
// ../components/button

// When you just want a centered button
export const ButtonBlockCenter = styled(Button)`
  margin-left: auto;
  margin-right: auto;
`
// Don't forget about using it in a flex container!
export const ButtonFlexCenter = styled(Button)`
  align-self: center;
`
// What about other alignments?
// You could pass in a prop, but this gets ugly:
// <ButtonFlex align="flex-end" />
export const ButtonFlex = styled(Button)`
  ${props => props.align ? `align-self: ${props.align};` : ''}
`
```

Real designs often come with edge cases, and any of these methods quickly get out of hand. How can we extend a child component _at the call site_, from the parent?

## Creating a Local Extension

One solution for simple cases is to create a local variation of the component by [extending it again](https://styled-components.com/docs/basics#extending-styles):

```jsx
import { Button } from '../components/button'

const CloseButton = styled(Button)`
  align-self: center;
`

const Modal = () => (
  <aside>
    <Button>Close</Button>
  </aside>
)
```

## Use Nesting Rules

If you already have a styled ancestor that provides enough context, you could use [nesting and references](https://styled-components.com/docs/advanced#referring-to-other-components) to extend your button. This can be more succinct if there are many children that require specific styles.

```jsx
import { Button } from '../components/button'

const Container = styled.aside`
  /* ... container styles */

  /* Maybe this will only apply to what we want... */
  > ${Button} {
    align-self: center;
  }
`

const Modal = () => (
  <Container>
    <Button>Close</Button>
  </Container>
)
```

#### Danger!

- Rules need to be specific
- Extensions of the child are also styled
- Fragments can be hiding more children at the same depth

If the rule written isn't specific enough it can have unexpected results on descendants. For example, a page wrapper that applies a descendant selector (`.parent .descendant`) instead of a child selector (`.parent > .child`) would effect any further descendant instances of the child. Also, any variations of the child or fragments that render the child at the top level would also get styled, and may not be obvious in the context of the page.

Check out this codepen for some examples of problems that can occur:


{% codepen https://codepen.io/bendman/pen/jOPwQeM default-tab=js,result %}

# Advanced Cases: Complex Children

What about passing styles to different elements of the child? This can be useful in complex child components, for example when they are used for layout of their own children, or have wrapping elements for more complex behavior.

One case of this pattern can be seen is in ReactNative, where [ScrollView takes a `contentContainerStyle`](https://reactnative.dev/docs/scrollview#contentcontainerstyle) and a normal `style` prop which are applied to different elements.

This can also be used on the web in custom components, like for styling a `<label>` inside of a `<FormField>` component, or adding a background color or inner alignment to a custom `<PageSection>` component.

While you can just apply styles directly, this avoids some of the benefits of `styled-components`, like CSS preprocessing and using props.

I'll be writing a future post that talks about these advanced use cases.