-
Notifications
You must be signed in to change notification settings - Fork 0
Styles
- Overview
- Passing Styles
- Pass Styles to Reference DOM Node
- Previous Value
- Pseudo States/Elements
- Media Queries
The styles prop of <Module> is what accepts the styles object/function.
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 = {
...
}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 }) => ({
...
});const styles = ({ context, state, theme, config, utils, element }) => ({
...
});Styles can either be in snake-case or camelCase:
const styles = {
fontSize: '14px',
'padding-bottom': '1em',
...
}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',
...
})
}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'
})
})
})
}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>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;
}... 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;
}
}
}
}
}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>
);const styles = {
content: {
color: 'grey'
},
myComponent: ({ state }) => ({
...(state.active && {
content: ({ state }) => ({
...(state.highlight && {
color: 'red',
fontWeight: 'bold'
})
})
})
})
}In Sass, you can reference parent selectors using the ampersand character. Using ES6's object spread syntax, you can simulate this use-case:
.wrapperElement {
font-size: 14px;
padding-bottom: 1em;
.someClass & {
font-size: 18px;
color: red;
}
}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'
})
})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];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'
}]
})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'
}Lucid provides several APIs to simulate pseudo states/selectors.
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
}
}
});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>const styles = {
target: ({ context }) => ({
...
...(context.wrapper.isHovered && {
...
})
})
}const styles = {
wrapper: ({ state }) => ({
...(state.isHovered && {
target: {
...
}
})
}),
target: {
...
}
}Using Cell Query Expressions
const styles = {
target: {
...
'wrapper:hover': {
...
}
}
}...or:
const styles = {
target: {
...
'in-wrapper': {
'and:hover': {
...
}
}
}
}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
}
}
});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
})
})
}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
contentproperty 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
})
}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.
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'
})
});

