Core
Theme
Below you can check the new updates, but you should also check the documentation to understand the improvements.
- Using the Admin UI theme just got easier. The
createSystem
function is not needed anymore, just import theThemeProvider
directly. - The way of styling a native JSX element was also simplified. Now, it is possible to import directly from
admin-ui
a function calledcsx
to create a className with the styles. Thecsx
function only uses the default theme under the hood. - The
ThemeProvider
provides a new prop calledexperimentalTheme
for those cases when you need to override the default theme and meet some specific design requirements. TheexperimentalTheme
prop will receive the new theme object. - The
ThemeProvider
provides a new boolean prop calledexperimentalDisabledGlobalStyles
to disable all the default global styles and enable the use of your own global styles. - The
colors
andtypography
theme tokens are now parsed to CSS variables.
ThemeProvider
Before
import { createSystem } from '@vtex/admin-ui'
const [ThemeProvider] = createSystem()
function Root() {
return {
<ThemeProvider>
{/* your app ... */}
</ThemeProvider>
}
}
After
import { ThemeProvider } from '@vtex/admin-ui'
function Root() {
return {
<ThemeProvider>
{/* your app ... */}
</ThemeProvider>
}
}
csx function
// import the csx function
import { csx } from '@vtex/admin-ui'
function Example() {
return (
<nav
className={csx({
bg: '$primary',
'button + button': {
marginLeft: '$s',
},
})}
/>
)
}
Overriding
1. Theme:
function RootComponent() {
const customTheme = {
colors: {
// all colors styles
},
fonts: {
// all font styles
},
// other theme props...
};
// Use at the root of your app
return (
<ThemeProvider experimentalTheme={customTheme}>
{/** your app code here */}
</ThemeProvider>
);
}
2. Global:
function RootComponent() {
// Use at the root of your app
return (
<ThemeProvider experimentalDisabledGlobalStyles>
{/** your app code here */}
</ThemeProvider>
)
}
Tokens as CSS variables
Button inspect then
Button inspect now
Deprecated tag.[el]
To reduce the library footprint, and enhance cohesion, the tag
factory is deprecated. You should use Box
instead. We have a codemod to help you with this migration:
npx admin-ui-codemod tag-to-box
Before
<tag.div
csx={
{
// your styles
}
}
/>
After
<Box
as="div"
csx={
{
// your styles
}
}
/>
fg alias
The csx accepts fg
as an alias for color:
<Box
csx={{
fg: '$primary',
}}
/>
Is shorthand for:
<Box
csx={{
color: '$primary',
}}
/>
colorTheme util
The csx accepts colorTheme
as a utility to apply color
and background
values of the same palette.
<Box
csx={{
colorTheme: 'blue',
}}
/>
is shorthand for:
<Box
csx={{
color: '$blue10',
backgroundColor: '$blue60',
}}
/>
negative util
Negative values and tokens are threatened as edge cases by the csx. You need the negative
function for this kind of transformation.
import { negative } from '@vtex/admin-ui'
function Example() {
return (
<Box
csx={{
marginLeft: '-1rem', // custom values work normally
marginRight: negative('$s'),
}}
/>
)
}
Colors as tokens
The bg
and fg
accepts colors from the palette as tokens.
Before
import { color } from '@vtex/admin-ui'
function Example() {
return (
<Box
csx={{
bg: color('blue60'),
}}
/>
)
}
After
<Box
csx={{
bg: '$blue60',
}}
/>
Responsive Property
Now in the layout components, you can use something similar to the responsive aliases within the component props. We call it Responsive Property. This is a replacement for the deprecated Responsive Arrays.
<Stack
space={{ mobile: '$l', desktop: '$2xl' }}
fluid={{ mobile: true, tablet: false }}
direction={{ mobile: 'column', tablet: 'row' }}
>
{...}
</Stack>
Components
Table
Below you can check the new updates, but you should also check the documentation to look for new features.
New Features
- It is possible to set columns to be fixed when scrolling horizontally. You just need to set the column prop
fixed
totrue
. - New resolver called
menu
to easily render a column with aMenu
component and a set of actions. - New resolver called
bulk
to add a bulk actions to the table. It is an easier way of using the newBulkActions
component within the Table.
1. Fixed columns
const state = useTableState({
columns: [
{
id: 'id',
fixed: true, // 1st fixed column
resolver: {
type: 'selection',
mapId: (item) => item.id,
},
},
{
id: 'name',
header: 'Name',
fixed: true, // 2nd fixed column
resolver: {
type: 'text',
columnType: 'name',
mapText: (item) => item.name,
},
},
// Other table columns...
],
})
2. Menu resolver
const state = useTableState({
columns: [
// Other table columns...
{
id: 'menu',
resolver: {
type: 'menu',
actions: [
{
label: 'Edit',
icon: <IconPencil />,
onClick: (item) => {
console.log(item)
},
},
{
label: 'Delete',
icon: <IconTrash />,
onClick: (item) => {
setItems(items.filter((i) => i.id !== item.id))
},
},
],
},
},
// Other table columns...
],
})
3. Bulk resolver
const pagination = usePaginationState({
pageSize: 100,
total: 5,
})
const bulk = useBulkActions({
totalItems: pagination.total,
pageItems: items,
pageSize: pagination.pageSize,
})
const table = useTableState({
view,
columns: [
{
id: 'id',
resolver: {
type: 'bulk',
state: bulk,
},
},
{
id: 'name',
header: 'Product Name',
},
// Other table columns...
],
items,
})
Bulk Actions
Now you can use the BulkActions
component to perform actions in a set of items. You can use it standalone or within the table. Check the documentation
Filters
Below you can check the new updates and breaking changes, but you should also check the documentation to learn about new features and usage examples.
New Features
- Filter status (
loading
,empty
,not found
,error
), that can be set by the developer. - "Optional filters" feature, witch allows filters to be initially hidden and have the visibility controlled by the user.
- New optional
onClear
callback on useFilterGroup - Composable components API that enables custom features or behaviours.
Breaking Changes
Filter
,FilterSearch
,FilterMultiple
andFilterSearchMultiple
no longer exist, all of them can be implemented using the new composable filter components.
Filter
Before
function Filter() {
const state = useFilterState({
items: [
{ label: 'Available', id: '#1' },
{ label: 'Sold out', id: '#2' },
],
onChange: () => {},
label: 'Status',
})
return <Filter state={state} />
}
After
function Filter() {
const filterState = useFilterState()
return (
<>
<FilterDisclosure state={filterState}>Status</FilterDisclosure>
<FilterPopover state={filterState}>
<FilterListbox>
<FilterOptionRadio id="#1" label="Available" />
<FilterOptionRadio id="#2" label="Sold out" />
</FilterListbox>
<FilterFooter />
</FilterPopover>
</>
)
}
FilterMultiple
Before
function FilterMultiple() {
const state = useFilterMultipleState({
items: [
{ label: 'Full', id: '#1' },
{ label: 'Empty', id: '#2' },
{ label: 'Half empty', id: '#3' },
],
onChange: () => {},
label: 'Status',
})
return <FilterMultiple state={state} />
}
After
function FilterMultiple() {
const filterState = useFilterMultipleState()
return (
<>
<FilterDisclosure state={filterState}>Status</FilterDisclosure>
<FilterPopover state={filterState}>
<FilterListbox>
<FilterOptionCheckbox id="#1" label="Full" />
<FilterOptionCheckbox id="#2" label="Empty" />
<FilterOptionCheckbox id="#3" label="Half Empty" />
</FilterListbox>
<FilterFooter />
</FilterPopover>
</>
)
}
FilterSearch
Before
function FilterSearch() {
const state = useFilterState({
items: [
{ label: 'Rio de Janeiro', id: '#1' },
{ label: 'New York', id: '#2' },
{ label: 'Paris', id: '#3' },
{ label: 'Tokyo', id: '#4' },
{ label: 'São Paulo', id: '#5' },
{ label: 'Berlin', id: '#7' },
{ label: 'Washington', id: '#8' },
{ label: 'Lisboa', id: '#9' },
{ label: 'Porto', id: '#10' },
{ label: 'João Pessoa', id: '#11' },
{ label: 'Salvador', id: '#12' },
{ label: 'Barcelona', id: '#13' },
],
onChange: ({ selected }) => console.log(`applied: ${selected.label}`),
label: 'City',
})
return <FilterSearch state={state} />
}
After
function FilterSearch() {
const filterState = useFilterState({
searchableList: [
{ label: 'Rio de Janeiro', id: '#1' },
{ label: 'New York', id: '#2' },
{ label: 'Paris', id: '#3' },
{ label: 'Tokyo', id: '#4' },
{ label: 'São Paulo', id: '#5' },
{ label: 'Berlin', id: '#7' },
{ label: 'Washington', id: '#8' },
{ label: 'Lisboa', id: '#9' },
{ label: 'Porto', id: '#10' },
{ label: 'João Pessoa', id: '#11' },
{ label: 'Salvador', id: '#12' },
{ label: 'Barcelona', id: '#13' },
],
})
return (
<>
<FilterDisclosure state={filterState}>Status</FilterDisclosure>
<FilterPopover state={filterState}>
<FilterSearchbox />
<FilterListbox>
{filterState.matches.map((item) => (
<FilterOptionRadio {...item} />
))}
</FilterListbox>
<FilterFooter />
</FilterPopover>
</>
)
}
FilterMultipleSearch
FilterSearch
Before
function FilterMultipleSearch() {
const state = useFilterMultipleState({
items: [
{ label: 'Rio de Janeiro', id: '#1' },
{ label: 'New York', id: '#2' },
{ label: 'Paris', id: '#3' },
{ label: 'Tokyo', id: '#4' },
{ label: 'São Paulo', id: '#5' },
{ label: 'Berlin', id: '#7' },
{ label: 'Washington', id: '#8' },
{ label: 'Lisboa', id: '#9' },
{ label: 'Porto', id: '#10' },
{ label: 'João Pessoa', id: '#11' },
{ label: 'Salvador', id: '#12' },
{ label: 'Barcelona', id: '#13' },
],
onChange: ({ selected }) => console.log(`applied: ${selected.label}`),
label: 'City',
})
return <FilterMultipleSearch state={state} />
}
After
function FilterMultipleSearch() {
const filterState = useFilterMultipleState({
searchableList: [
{ label: 'Rio de Janeiro', id: '#1' },
{ label: 'New York', id: '#2' },
{ label: 'Paris', id: '#3' },
{ label: 'Tokyo', id: '#4' },
{ label: 'São Paulo', id: '#5' },
{ label: 'Berlin', id: '#7' },
{ label: 'Washington', id: '#8' },
{ label: 'Lisboa', id: '#9' },
{ label: 'Porto', id: '#10' },
{ label: 'João Pessoa', id: '#11' },
{ label: 'Salvador', id: '#12' },
{ label: 'Barcelona', id: '#13' },
],
})
return (
<>
<FilterDisclosure state={filterState}>Status</FilterDisclosure>
<FilterPopover state={filterState}>
<FilterSearchbox />
<FilterListbox>
{filterState.matches.map((item) => (
<FilterOptionChecbox {...item} />
))}
</FilterListbox>
<FilterFooter />
</FilterPopover>
</>
)
}
Toast component review
New Features
- Now you can replace an active toast with another one. Only a single toast with a given id is visible at once and you can use it to reduce the number of toasts on your app. Check the documentation
Breaking Changes
- The
tone
property has changed tovariant
- Now all the toasts are dismissible by default, you should use the
dismissible
prop with the valuefalse
to change it.
Before
const showToast = useToast()
showToast({
tone: 'critical',
message: 'Type a short message here',
dismissible: true,
action: {
label: 'Action',
onClick: () => // do something,
},
})
After
const showToast = useToast()
showToast({
variant: 'critical',
message: 'Type a short message here',
action: {
label: 'Action',
onClick: () => // do something,
},
})
Card component review
Added CardHeader
, CardInfo
, CardTitle
, CardActions
, CardContent
, and CardImage
composites. You can use them to help you implement card layouts while reusing the definitions from the design team to the spacing and positioning of the elements.
Before
<Card>
{...}
</Card>
After
// Basic example
<Card>
{...}
</Card>
// Full example
<Card>
<CardHeader>
<CardInfo>
<IconImageSquare />
<CardTitle>Title</CardTitle>
<Tag label="Short text" />
</CardInfo>
<CardActions>
<Button variant="tertiary">Label</Button>
<Button variant="secondary">Label</Button>
</CardActions>
</CardHeader>
<CardContent>{...}</CardContent>
</Card>
Flex component review
Now all of the custom properties have the type ResponsiveProp
Grid component review
Now all of the custom properties have the type ResponsiveProp
Columns component review
New features
- Now all of the custom properties have the type ResponsiveProp
Breaking Changes
- The
spacing
property has changed tospace
. - You can’t use Responsive Arrays with the
units
andoffset
properties anymore, you must use ResponsiveProps instead.
Before
<Columns spacing={1}>
<Column units={6} offset={['right', 'right', 'none']}>{...}</Column>
<Column units={[3, 2, 1]}>{...}</Column>
</Columns>
After
<Columns space="1">
<Column units={6} offset={{ mobile: 'right', desktop: 'none'}}>
{...}
</Column>
<Column units={{ mobile: 3, tablet: 2, desktop: 1 }}>
{...}
</Column>
</Columns>
Set
This component was removed in this release. You must use Stack now. We have a codemod to help you in this migration.
npx admin-ui-codemod set-to-stack