Understanding Overrides

By the end of this guide, you'll learn the concept of overrides in Spaceweb, and the ways you can use them to change the look or the behavior of components.

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:

Select
open

This component is very self-contained and you can load it through a single import:

import { StatefulSelect } from '@sprinklrjs/spaceweb/select'
export default () => (
<StatefulSelect
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' },
]}
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 (
<StatefulSelect
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' },
]}
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):

Select...
open

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:

<StatefulSelect
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' },
]}
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:

Select 1
open
Select 2
open
Select 3
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:

<Foo
overrides={{
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.

<Foo
overrides={{
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:

search
Atlanta
Selected Atlanta. Use Left and Right arrow keys to navigate between selected values. Enter BackSpace to remove currently focused value

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:

search
Atlanta
Selected Atlanta. Use Left and Right arrow keys to navigate between selected values. Enter BackSpace to remove currently focused value

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 (
<StatefulSelect
options={items}
labelKey="id"
valueKey="color"
overrides={{
DropdownContainer: {
component: (props) => (
<Typography className="typography-b1 p-2">
No options found
</Typography>
)
}
}}
/>
);
};

The result:

Select...
open

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:

<StatefulSelect
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' },
]}
overrides={{DropdownContainer: StyledDropdownContainer}}
/>
<StatefulSelect
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' },
]}
/>

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 styles
const 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 - undefined
const [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 style
const 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.