Understanding Overrides
Base Web is a set of reusable React components that implements the Base Web design language and can be used in two different ways:
- To build an application that fully adopts the Base Web design language, you import and use Base Web components out of the box.
- To build a new design system inherited from the Base, you take Base Web components and customize them through the Overrides mechanism.
If you are building an application using the Base Web design language (the first scenario), you should avoid further customization. This helps to keep the design of your application consistent and makes future upgrades easier. Overrides is an escape hatch that should be used only with the great caution!
Subcomponents
Base Web components typically consist of many subcomponents. Let's use baseui/dnd-list
as an example:
- Item 1
- Item 2
- Item 3
This component is very self-contained and you can load it through a single import:
import {StatefulList} from 'baseui/dnd-list'; export default () => ( <StatefulList initialState={{ items: ['Item 1', 'Item 2', 'Item 3'], }} /> );
But as you might guess, there are multiple React components under the hood, components like Item
, DragHandle
or Label
. They all come with various styles, behaviors and attributes. We call them subcomponents.
Introducing Overrides
Overrides gives you a full access to all those subcomponents and lets you to override:
- styles of the subcomponent
- props of the subcomponent
- the whole subcomponent
Every Base Web 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:
<StatefulList initialState={{ items: ['Item 1', 'Item 2', 'Item 3'], }} overrides={{ Label: { style: { color: '#892C21', }, props: { 'data-test-id': 'dnd-list-label', }, }, }} />
We defined overrides.Label.style
and overrides.Label.props
properties and this is the result (inspect the element to see the data-test-id
attribute):
- Item 1
- Item 2
- Item 3
The overrides.Label.style
property accepts a style object or style function since Styletron manages all Base Web styles.
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!
$theme
If you opt-in for the style function, overrides
provides a special prop called $theme
that you can use. The $theme
prop includes all Base Web design constants. So instead of the hard-coded value #892C21
, you can use the theme:
<StatefulList initialState={{ items: ['Item 1', 'Item 2', 'Item 3'], }} overrides={{ Label: { style: ({$theme}) => ({ color: $theme.colors.negative600, }), }, }} />
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 Label
comes with:
$isDragged: boolean
-true
if the list item is dragged$isSelected: boolean
-true
if the list item is selected (space key-press)$isRemovable: boolean
-true
if the list item is removable$value: React.Node
- item's value$index: number
- item's index
Let's use $isDragged
to change the Label color when dragged:
<StatefulList initialState={{ items: ['Item 1', 'Item 2', 'Item 3'], }} overrides={{ Label: { style: ({$theme, $isDragged}) => ({ color: $isDragged ? $theme.colors.primary : $theme.colors.negative600, }), }, }} />
The result is that the Label turns blue when it's dragged:
- Item 1
- Item 2
- Item 3
Overrides Inspector
Almost every Base Web component has multiple overrides. How can you learn what's available to you? Every component page has the Overrides
section at the bottom. It lists all overridable subcomponents and highlights each one of them. This section is taken straight from the dnd-list page:
Additionally, you can fully customize any part of the Drag and Drop List through the overrides prop. The Drag and Drop List consists of multiple subcomponents that are listed bellow and you can override each one of them. To help you identify the names of these subcomponents, you can highlight them through this selector:
- Item 1
- Item 2
- Item 3
- Item 4
Note: You should always use longhand CSS properties. Mixing shorthands and longhands will lead into strange behaviors!
If you are interested in which state props your style functions can use, you'll find the list of them at the bottom of every component page. Scroll down to the API section and click on the Expand Prop Shape
button for the overrides
property.
Override Nested Components
Some Base Web components use other Base Web components internally. Examples include the Select
component, which uses the Tag
component
for the multi-select mode, or the FileUploader
which uses the Button
component.
In these cases, you are not directly modifying styled components, but nested components. To make that happen, you have to pass in your overrides
through the props
override functionality.
To better understand it, let's take a look at the example of the Select
component:
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 Base Web components. For example, we can enhance our textual Label and add a cloning functionality:
import * as React from 'react'; import {List, arrayMove} from 'baseui/dnd-list'; export default class Example extends React.Component { state = { items: ['Car', 'Truck', 'Bike', 'Skateboard'], }; render() { return ( <List items={this.state.items} onChange={({oldIndex, newIndex}) => this.setState(prevState => ({ items: arrayMove(prevState.items, oldIndex, newIndex), })) } overrides={{ Label: { component: ({$value}) => ( <div style={{flexGrow: 1}}> {$value}{' '} <button onClick={() => this.setState(prevState => ({ items: prevState.items.concat([`${$value} clone`]), })) } > Clone </button> </div> ), }, }} /> ); } }
The result:
- Car
- Truck
- Bike
- Skateboard
Note that we lost the original Label styling since we replaced the whole Label subcomponent. If you still want to reuse or compose the original subcomponent you can import it:
import {StyledLabel} from 'baseui/dnd-list';
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 Base Web components are implemented underneath:
<StatefulList initialState={{items: ['A', 'B', 'C']}} overrides={{Label: StyledLabel}} /> <StatefulList initialState={{items: ['A', 'B', 'C']}} />
This technique gives you a ridiculous amount of flexibility. However, with great power comes great responsibility. We might not be able to effectively support you if you run into issues and upgrades to future versions of Base Web can be complicated. If you have a need to change components behavior this way, you should first ask maintainers of Base Web. We might add your feature through an official API instead so you don't need to use this override.
To learn more about how overrides work internally, check out the Better Reusable React Components with the Overrides Pattern article.