Skip to content
esr360 edited this page Feb 16, 2020 · 6 revisions

Overview

The styles prop of <Module> is what accepts the styles object/function.

Styles as an Object

If you don't need to apply styles to any elements based off the parent's state/props or context, your styles can exist as an object:

const styles = {
  ...
}
Styles as a Function

If you need to apply styles to any element based off the parent's state/props or context, your styles should exist as a function that returns an object (or an array):

const styles = ({ context, state }) => ({
  ...
});
Styles as a Function (Complete)
const styles = ({ context, state, theme, config, utils, element }) => ({
  ...
});
Learn More About Available Parameters

Passing Styles

Styles can either be in snake-case or camelCase:

const styles = {
  fontSize: '14px',
  'padding-bottom': '1em',
  ...
}

Passing Styles To Child Components

As all Components should have a name prop, you can map styles to Components using the value of the name prop as a key. As with the main styles object/function, styles for Components can also exist as either an object or a function, depending on whether or not you need supply styles based off any state/props or context.

const styles = {
  myComponent: {
    ...
  },

  anotherComponent: ({ state }) => ({
    color: state.active ? 'red' : 'blue',
    ...
  })
}

Nesting/Cascading Component Styles

Whilst when using Lucid it's recommend to use context to determine parent context, you can also nest Component styles within parent Components to simulate cascading (similar to how context is determined in CSS).

In the following example, the styles for anotherComponent will only be applied when the Component is a child of myComponent:

const styles = {
  anotherComponent: {
    color: 'red'
  },

  myComponent: {
    anotherComponent: {
      color: 'blue'
    }
  }
}

const MyModule = (props) => (
  <Module styles={styles}>
    <Component name='myComponent'>
      <Component name='anotherComponent'>I'm blue</Component>
    </Component>

    <Component name='anotherComponent'>I'm Red</Component>
  </Module>
);

The same outcome could also be achieved without cascading using context (recommended):

const styles = {
  anotherComponent: ({ context }) => ({
    color: context.myComponent ? 'blue' : 'red'
  })
}

...or:

const styles = {
  anotherComponent: ({ context }) => ({
    color: 'red',

    ...(context.myComponent && {
      color: 'blue'
    })
  })
}

You can also pass functions instead of objects as normal:

const styles = {
  anotherComponent: {
    color: 'red'
  },

  myComponent: ({ state }) => ({
    ...(state.active && {
      anotherComponent: {
        color: 'blue'
      }
    })
  })
}

The same outcome could also be achieved without cascading using context (recommended):

const styles = {
  anotherComponent: ({ context }) => ({
    color: context.myComponent && context.myComponent.active ? 'blue' : 'red'
  })
}

...or:

const styles = {
  anotherComponent: ({ context }) => ({
    color: 'red',

    ...(context.myComponent && {
      ...(context.myComponent.active && {
        color: 'blue'
      })
    })
  })
}
In-Depth Example
HTML

Consider some simple HTML showing elements with stateful classes (active and highlight):

<div class="myModule">
  <div class="myComponent active">
    <div class="content highlight">Some content</div>
  </div>
</div>
CSS

You could apply some styles using plain CSS, such as:

This wouldn't be considered 'good' CSS, it's just to demonstrate basic principles

.myModule .content {
  color: grey;
}

.myModule .myComponent.active .content.highlight {
  color: blue;
}
Sass

... or by using Sass + nesting:

...again, this level of nesting isn't good, but just to demonstrate...

.myModule {
  .content {
    color: grey;
  }

  .myComponent {
    &.active {
      .content {
        &.highlight {
          color: blue;
          font-weight: bold;
        }
      }
    }
  }
}
ES6 + Lucid (using context - recommended)

The above outcome can be simulated using Lucid and context:

const styles = {
  content: ({ context, state }) => ({
    color: 'grey',

    ...(state.highlight && context.myComponent && context.myComponent.active && {
      color: 'red',
      fontWeight: 'bold'
    })
  })
}

const MyModule = () => (
  <Module styles={styles}>
    <Component name='myComponent' active>
      <Component name='content' highlight>Some content</Component>
    </Component>
  </Module>
);
ES6 + Lucid (using nesting)
const styles = {
  content: {
    color: 'grey'
  },

  myComponent: ({ state }) => ({
    ...(state.active && {
      content: ({ state }) => ({
        ...(state.highlight && {
          color: 'red',
          fontWeight: 'bold'
        })
      })
    })
  })
}

Simulating Sass Ampersand Parent-Selector Referencing

In Sass, you can reference parent selectors using the ampersand character. Using ES6's object spread syntax, you can simulate this use-case:

Sass
.wrapperElement {
  font-size: 14px;
  padding-bottom: 1em;

  .someClass & {
    font-size: 18px;
    color: red;
  }
}
ES6 JavaScipt
const styles = {
  fontSize: '14px',
  'padding-bottom': '1em',
  
  ...(someCondition && {
    fontSize: '18px',
    color: 'red'
  })
}

This allows you to group multiple styles together based off some condition, that would typically come from state/props or context:

const styles = ({ state }) => ({
  backgroundColor: 'transparent',
  color: '#424242',
  
  ...(state.active && {
    backgroundColor: 'red',
    color: 'white'
  }),

  ...(state.isHovered && {
    backgroundColor: 'green',
    color: 'white'
  })
})

Pass Array of Styles

You can pass an array of styles, nested infinitely, which will be executed in order of sequence.

const styles = [
  {...}, 
  ({ state}) => ({...}), 
  [...]
];

This is useful for retreiving cosmetic stylistic properties from things like config and state:

const styles = ({ config, state }) => [{
  // some styles
}, config, state];

Passing Styles To Sub-Components

Please see the SubComponent page

Pass Styles to Reference DOM Node

It's possible to pass styles to a reference DOM node. This is generally not recommended, but can be useful in some situations. To do this, choose a key name and assign an array, where the first value is a function that returns the DOM node, and the second value is a styles object:

const styles = ({ element }) => ({
  parentNode: [() => element.parentNode, {
    marginBottom: '10px'
  }],
  body: [() => document.body, {
    overflow: 'hidden'
  }]
})

Previous Value

You can access the previous value of a property by passing the value as a function and passing a prev parameter:

const styles = {
  display: (prev) => prev === 'none' ? 'block' : 'none'
}

Pseudo States/Elements

Lucid provides several APIs to simulate pseudo states/selectors.

Hover

Hover styles can be applied using either the :hover keyword as a key, or by accessing the isHovered property of state:

const styles = ({ state }) => ({
  color: state.isHovered ? 'red' : 'blue',

  ...(state.isHovered && {
    // ...some styles
  }),

  myComponent: {
    ':hover': {
      // ...some styles
    }
  }
});

Apply Styles Based on Parent Hover

In the below example we will apply styles to the target Component when the wrapper Component is hovered.

<Module styles={styles}>
  <Component name='wrapper'>
    ...
    <Component name='target'>...</Component>
  </Component>
</Module>
Using Context (recommended)
const styles = {
  target: ({ context }) => ({
    ...

    ...(context.wrapper.isHovered && {
      ...
    })
  })
}
Using State
const styles = {
  wrapper: ({ state }) => ({
    ...(state.isHovered && {
      target: {
        ...
      }
    })
  }),

  target: {
    ...
  }
}
const styles = {
  target: {
    ...

    'wrapper:hover': {
      ...
    }
  }
}

...or:

const styles = {
  target: {
    ...

    'in-wrapper': {
      'and:hover': {
        ...
      }
    }
  }
}

Focus

Focus styles can be applied using either the :focus keyword as a key, or by accessing the isFocused property of state:

const styles = ({ state }) => ({
  color: state.isFocused ? 'red' : 'blue',

  ...(state.isFocused && {
    // ...some styles
  }),

  myComponent: {
    ':focus': {
      // ...some styles
    }
  }
});

First/Last Child

You can determine if an element is either the first or last child of its parent by accessing the isFirstChild and isLastChild properties of state.

const styles = {
  target: ({ state }) => ({
    ...(state.isFirstChild && {
      // some styles
    }),
    ...(state.isLastChild && {
      // some styles
    })
  })
}

Before/After

You can simulate the before/after pseudo elements by passing the :before and :after keywords. Note that this will create an actual DOM node, unlike the real thing. Before using this feature, consider simply adding a new compoent to your Module.

Note that you do not need to pass a content property for this to work, unlike the real thing

const styles = {
  ':after': {
    // ... some styles
  },

  targetComponent: ({ state }) => ({
    ':before': {
      // ... some styles
    }
  })
}

The :before and :after elements can be styled based off context like regular Modules/Components, by passing the styles as a function:

const styles = {
  targetComponent: ({ state }) => ({
    ...,
  
    ':before': ({ context }) => ({
      // ... some styles
    })
  })
}

Passing React Element

import Button from '../components/button';

const styles = {
  ':before': {
    content: <Button>Some Button</Button>,
    // ... some styles
  },
  
  targetComponent: ({ state }) => ({
    ':after': {
      content: <h3>Some Heading</h3>,
      // ... some styles
    },
    // ... some styles
  })
}

Nesting

Whilst it's not recommended to do this, you can nest :before/:after elements within other :before/:after elements:

const styles = {
  targetComponent: ({ state }) => ({
    ...,
  
    ':before': {
      // ... some styles

      ':after': {
        // ... some styles
      }
    }
  })
}

This isn't so much a 'feature' of Lucid per se, but more of a side-effect of how it's built. If you want to exploit it, go ahead.

Media Queries

Using ES6’s object spread syntax combined with the matchMedia web API, you can easily simulate media queries with just plain JavaScript:

const styles = {
  display: 'flex',

  ...(window.matchMedia('(max-width: 960px').matches && {
    display: 'block'
  })
}

Combined with Lucid’s API you could make this much nicer by creating a utility function, and use it like so:

const styles = ({ theme }) => ({
  display: 'flex',
  
  // You would need to create `theme.media()`
  ...(theme.media('medium') && {
    display: 'block'
  })
});

Clone this wiki locally