Understanding Overrides
Spaceweb is a set of reusable React components that fully adopts the Space design language.
Subcomponents
Spaceweb components typically consist of many subcomponents. Let's use @sprinklrjs/spaceweb/select
as an example:
This component is very self-contained and you can load it through a single import:
import { StatefulSelect } from '@sprinklrjs/spaceweb/select'export default () => (<StatefulSelectoptions={[{ id: 'AliceBlue', color: '#F0F8FF' },{ id: 'AntiqueWhite', color: '#FAEBD7' },{ id: 'Aqua', color: '#00FFFF' },{ id: 'Aquamarine', color: '#7FFFD4' },{ id: 'Azure', color: '#F0FFFF' },{ id: 'Beige', color: '#F5F5DC' },]}labelKey="id"valueKey="color"placeholder="Select"/>);
But as you might guess, there are multiple React components under the hood, components like Placeholder
, DropdownContainer
, ControlContainer
or MenuItem
. They all come with various styles, behaviors and attributes. We call them subcomponents.
Introducing Overrides
Overrides gives you full access to all those subcomponents and lets you to override:
- styles of the subcomponent
- props of the subcomponent
- the whole subcomponent
Every Spaceweb component has a top-level prop called overrides
. It accepts a map of subcomponents and desired overrides. For example, if we want to change the Label
's color and also add an additional data-test-id
attribute (props are spread over the subcomponent), we can do:
import { StatefulSelect } from '@sprinklrjs/spaceweb/select';export default () => {return (<StatefulSelectoptions={[{ id: 'AliceBlue', color: '#F0F8FF' },{ id: 'AntiqueWhite', color: '#FAEBD7' },{ id: 'Aqua', color: '#00FFFF' },{ id: 'Aquamarine', color: '#7FFFD4' },{ id: 'Azure', color: '#F0FFFF' },{ id: 'Beige', color: '#F5F5DC' },]}labelKey="id"valueKey="color"overrides={{Placeholder: {style: {color: 'blue',},props: {'data-test-id': 'select-label',},},}}/>);};
We defined overrides.Placeholder.style
and overrides.Placeholder.props
properties and this is the result (inspect the element to see the data-test-id
attribute):
The overrides.Placeholder.style
property accepts any Styles and is handled by Spaceweb's styled utility internally.
For more information on how styled
works, refer to How Styles are Parsed?
Caveat: When using
overrides.foo.style
, you are overriding a set of existing CSS properties. Our components always use longhand CSS properties and so should yours! If you mix shorthand and longhand properties, you will see a warning and can run into strange behaviors!
Style Function
If you opt-in for the style function,
overrides
provides all the style utils.
So instead of the hard-coded value #892C21
, you can use the theme:
<StatefulSelectoptions={[{ id: 'AliceBlue', color: '#F0F8FF' },{ id: 'AntiqueWhite', color: '#FAEBD7' },{ id: 'Aqua', color: '#00FFFF' },{ id: 'Aquamarine', color: '#7FFFD4' },{ id: 'Azure', color: '#F0FFFF' },{ id: 'Beige', color: '#F5F5DC' },]}labelKey="id"valueKey="color"overrides={{Placeholder: {style: ({ theme }, props) => ({color: theme.spr.highlight,}),},}}/>
State Props
The prop $theme
is not the only variable that you can use in your style function. Most of subcomponents get various state props. For example, the ControlContainer
comes with:
$disabled: boolean
$error: boolean
$isFocused: boolean
$type: "select" | "search"
$searchable: boolean
$size: "mini" | "default" | "compact" | "large"
$isOpen: boolean
Let's use $isFocused
to change the ControlContainer border color when open:
export default () => {const options = [{ id: 'AliceBlue', color: '#F0F8FF' },{ id: 'AntiqueWhite', color: '#FAEBD7' },{ id: 'Aqua', color: '#00FFFF' },{ id: 'Aquamarine', color: '#7FFFD4' },{ id: 'Azure', color: '#F0FFFF' },{ id: 'Beige', color: '#F5F5DC' },];const overrides = {ControlContainer: {style: ({ theme }, { $isOpen }) => ({borderColor: $isOpen ? theme.spr.favourite : theme.spr.ui01,}),},};const props = { options, labelKey: 'id', valueKey: 'color', overrides };return (<><StatefulSelect {...props} placeholder="Select 1" /><StatefulSelect {...props} placeholder="Select 2" /><StatefulSelect {...props} placeholder="Select 3" /></>);};
The result is that the ControlContainer turns orange when it's open:
Exploring Overrides
Almost every Spaceweb component has multiple overrides. How can you learn what's available to you? Every component page has the Style Overrides
section at the top of the page. It lists all overridable subcomponents and highlights each one of them once activated. Using this editor, you can also change the style properties of each override, so you can see instantly how the modified components would look like.
If you are interested in which state props your style functions can use, you'll find the list of them by clicking the "Toggle shared props" button in the overrides.
Override Nested Components
Every Spaceweb component exposes the override
prop. When one Spaceweb component uses another Spaceweb component internally we have access to a new, powerful pattern: Nested Overrides.
The idea is to use a component's overrides
prop to access a nested component and pass this nested component an overrides
prop of its own. You end up with a nested structure like so:
<Foooverrides={{Boo: {props: {overrides: {// pass "nested" overrides to the inner "Boo" component},},},}}/>
This is a very reliable method for customizing deeply nested components. It is possible because everywhere a Spaceweb component uses another Spaceweb component, that ‟parent” component will expose an overrides
property for the ‟child” component. In theory, you could nest overrides as many levels deep as necessary to customize something.
<Foooverrides={{Boo: {props: {overrides: {Moo: {props: {overrides: {Zoo: {props: {overrides: {Goo: () => 'hey mom!',},},},},},},},},},}}/>
In this example, we use four nested overrides to replace the Goo
component. We do this without affecting the four components above Goo
. We've avoided having to re-implement layers and layers of logic, and, because the interface is consistent, you can focus on where you want to drop in, rather than how to do it.
If you find the exact syntax of this technique a little difficult to recall, try instead to remember that every Spaceweb component exposes an overrides
prop and that every nested Spaceweb component is made accessible as an overrides
property in the top-level component.
The path always follows this pattern:
ComponentA > overrides > ComponentB > props > overrides
A more practical example
Here is the default multi-select option for Spaceweb:
Let's change the way the selected values appear.
The first step is to identify what we want to override. In this case, we want to change the selected values for a multi-select. Let's check out the overrides
inspector for Select. Looking through the list of possible overrides, we see there is a promising MultiValue
property.
You might notice that the MultiValue
component is just an instance of the Tag
component. We can reference the documentation for Tag to see what overrides are available.
We have identified a nested Spaceweb component that is accessible via overrides. Now we can apply some nested overrides to customize things:
Notice the nested override pattern here:
StatefulSelect > overrides > MultiValue > props > overrides
Once we have access to the nested Tag
, we can override the styles for the Root
, Text
, Action
& ActionIcon
, changing the colors to a few lovely shades of purple.
Override The Entire Subcomponent
This is a very advanced technique and rarely needed. If you go down this path, you might also need to inspect our source code to fully understand all behaviors that subcomponents should/can implement.
So far we demonstrated how to override styles or add additional props but you can also completely replace subcomponents. This means you can alter the behavior and appearance of all Spaceweb components. For example, we can enhance our textual Placeholder and add a button :
import { StatefulSelect } from '@sprinklrjs/spaceweb/select';import { Button } from '@sprinklrjs/spaceweb/button'export default () => {const items = [{ id: 'AliceBlue', color: '#F0F8FF' },{ id: 'AntiqueWhite', color: '#FAEBD7' },{ id: 'Aqua', color: '#00FFFF' },{ id: 'Aquamarine', color: '#7FFFD4' },{ id: 'Azure', color: '#F0FFFF' },{ id: 'Beige', color: '#F5F5DC' },];return (<StatefulSelectoptions={items}labelKey="id"valueKey="color"overrides={{DropdownContainer: {component: (props) => (<Typography className="typography-b1 p-2">No options found</Typography>)}}}/>);};
The result:
Note that we lost the original DropdownContainer styling since we replaced the whole DropdownContainer subcomponent. If you still want to reuse or compose the original subcomponent you can import it:
import { StyledDropdownContainer } from '@sprinklrjs/spaceweb/select';
The named import always matches the override key with an addition of Styled
prefix. Following two examples yield the exactly same result since this is how Spaceweb components are implemented underneath:
<StatefulSelectoptions={[{ id: 'AliceBlue', color: '#F0F8FF' },{ id: 'AntiqueWhite', color: '#FAEBD7' },{ id: 'Aqua', color: '#00FFFF' },{ id: 'Aquamarine', color: '#7FFFD4' },{ id: 'Azure', color: '#F0FFFF' },{ id: 'Beige', color: '#F5F5DC' },]}overrides={{DropdownContainer: StyledDropdownContainer}}/><StatefulSelectoptions={[{ id: 'AliceBlue', color: '#F0F8FF' },{ id: 'AntiqueWhite', color: '#FAEBD7' },{ id: 'Aqua', color: '#00FFFF' },{ id: 'Aquamarine', color: '#7FFFD4' },{ id: 'Azure', color: '#F0FFFF' },{ id: 'Beige', color: '#F5F5DC' },]}/>
Understanding useOverrides and mergeOverrides in Spaceweb
In modern web development, flexibility and customization are key. Developers need tools that allow them to easily modify components and styles.
useOverrides
and mergeOverrides
are two such tools provided by the Spaceweb library.
In this article, we will explore what these utilities do, and how they can be used to enhance your development process.
The Power of useOverrides
Imagine you need to create an Input component with a StartEnhancer, using the simple structure defined below.
<Root><StartEnhancer /><input /></Root>
Spaceweb provides a powerful overrides
prop that allows you to make various modifications to the original component.
Let's add this same functionality to our Input component and understand how useOverrides
can help in this context.
import { useOverrides, Override } from '@sprinklrjs/spaceweb/overrides';import { styled } from '@sprinklrjs/spaceweb/style';// Component with default stylesconst DefaultRoot = styled('div', 'rounded-4');const DefaultStartEnhancer = styled('div', 'p-2 inline-flex items-center justify-center');const DefaultInput = styled('input', 'text-14 px-2 py-1', theme => ({ '::placeholder': { color: theme.spr.text02 });type Props = {overrides?: {Root?: Override;StartEnhancer?: Override;Input?: Override;}}// Next, we create our Input component using useOverrides. This allows us to modify the Root, StartEnhancer, and Input components based on the overrides prop.const Input = ({ overrides }) => {const [Root, rootProps] = useOverrides(overrides?.Root, DefaultRoot);const [StartEnhancer, startEnhancerProps] = useOverrides(overrides?.StartEnhancer, DefaultStartEnhancer);const [Input, inputProps] = useOverrides(overrides?.Input, DefaultInput);return (<Root {...rootProps}><StartEnhancer {...startEnhancerProps} /><Input placeholder="input" {...inputProps} /></Root>)}
What happens behind the scenes?
The useOverrides
function returns two values: ComponentToRender
and componentProps
which should be passed to ComponentToRender
.
The consumer can override the style, props, and the entire component itself with Override
.
useOverrides
accepts a DefaultComponent
which will be rendered if override.component
is not provided.
The componentProps
returned by useOverrides
will be the same as override.props
, but it adds an additional $style
prop if override.style
is defined.
Consider the following examples to understand how useOverrides
works:
const [Component, componentProps] = useOverrides({ props: { newProp: 1 } }, DefaultComponent);// Component - DefaultComponent// componentProps - { newProp: 1 }const [Component, componentProps] = useOverrides(NewComponent, DefaultComponent);// Component - NewComponent// componentProps - undefinedconst [Component, componentProps] = useOverrides({ component: NewComponent, props: { newProp: 1 }, style: { color: 'red' } },DefaultComponent);// Component - NewComponent// componentProps - { newProp: 1, $style: { color: 'red' } }
To support style override, ComponentToRender
should know how to handle $style
.
In most cases, DefaultComponent
is a component created with the styled
utility, which is capable of handling $style
.
Merging with mergeOverrides
The mergeOverrides
function, as the name suggests, combines two overrides
.
This utility should be your go-to solution for merging any two overrides, as it optimally merges styles and nested overrides. Let's look at the different scenarios it covers.
const mergedOverrides = mergeOverrides({ Root: { style: { color: 'red' } } }, { Root: { style: { display: 'none' } } });// mergedOverrides === { Root: { style: [{ color: 'red' }, { display: 'none' }] } }// here [{ color: 'red' }, { display: 'none' }] is a valid styleconst mergedOverrides = mergeOverrides({ Root: NewRootComponent },{ Root: { props: { onClick: () => console.log('clicked') } } });// mergedOverrides === { Root: { component: NewRootComponent, props: { onClick: () => console.log('clicked') } } }
It's worth noting that mergeOverrides
also supports the merging of nested overrides.
const mergedOverrides = mergeOverrides({ Popover: { props: { showArrow: true, overrides: { Body: { style: { backgroundColor: 'red' } } } } } },{ Popover: { props: { overrides: { Body: { style: ({ theme }) => ({ color: theme.spr.text02 }) } } } } });/**mergedOverrides ==={Popover: {props: {showArrow: true,overrides: { Body: { style: [{ backgroundColor: 'red' }, ({ theme }) => ({ color: theme.spr.text02 })] } },},},};*/
In conclusion, useOverrides
and mergeOverrides
are powerful utilities provided by Spaceweb that can drastically improve the customization capabilities of your components.
They provide a systematic and effective way of overriding styles, props, and even entire components.
Understanding these tools will allow you to write more flexible and maintainable code.