Version 8 to 9
This migration guide contains migration instructions for:
- eightshift/boilerplate - 8+ --> 9.0.0
 - eightshift/frontend-libs - 7+ --> 8.0.0
 - eightshift/libs - 6.4.0
 
Required changes
Migration time: ~45min to a couple of hours, depending on project size and components used.
- Update 
composerpackages and check if eightshift Libs are on version 6.4 (or higher) - Update 
@eightshift/frontend-libsin yourpackage.jsonfile to the latest version:"@eightshift/frontend-libs": "^8.0.0", - Important: Delete your lockfile (
package.lock) and yournode_modulesfolder, then runnpm install - Rename updated components, update changed properties and replace deprecated components (see chapter below)
 - Do a 
npm start, check that you have no build errors visible - Smoke test your blocks - to verify everything was migrated properly, go through all the blocks and check if everything looks good visually and that everything is functional
 
Component updates and replacements
Below you will find some of the more common components that will need to be modified, and also some possible caveats.
Your code editor should mark the components that need replacement with a strikethrough over the component name:

Common between components
- if you have a label with an 
IconLabelinside, you can migrate it to separateiconandlabelprops:becomeslabel={<IconLabel icon={icons.color} label={__('Background', 'domain')} />}icon={icons.color}
label={__('Background', 'domain')} - if the component is the last in the list of options, you can add 
noBottomSpacingto make everything look a bit nicer - if you want to visually group two similar components, e.g. toggles, you can bring them vertically closer together with 
reducedBottomSpacing 
CollapsableComponentUseToggle
- rename the component and imports to 
UseToggle - change 
showUseToggletonoUseToggleand flip its logic - change 
showLabeltonoLabeland flip its logic - change 
showExpanderButtontonoExpandButtonand flip its logic - consider adding 
noExpandButtonto your attributes and add it to all your blocks, so the options render nicely 
ComponentUseToggle
- replace the component with 
UseToggle - move all of the options inside the useToggle
 - change 
showUseToggletonoUseToggleand flip its logic - change 
showLabeltonoLabeland flip its logic 
Example
Before
<ComponentUseToggle
	label={label}
	checked={accordionUse}
	onChange={(value) => setAttributes({ [getAttrKey('accordionUse', attributes, manifest)]: value })}
	showUseToggle={showAccordionUse}
	showLabel={showLabel}
/>
{accordionUse &&
	// Other options.
}
After:
<UseToggle
	label={label}
	checked={accordionUse}
	onChange={(value) => setAttributes({ [getAttrKey('accordionUse', attributes, manifest)]: value })}
	noUseToggle={!showAccordionUse} // Inverted logic!
	noLabel={!showLabel} // Inverted logic!
	noBottomSpacing // If the component is the only one in the options panel
>
	// Other options.
</UseToggle>
ColorPaletteCustom
- rename the component and imports to 
ColorPalette - remove the 
inlineprop if you have it added - change the 
layoutprop to a string instead of aColorPaletteCustomLayoutobject (hint: use autocomplete to see all the possible values) - if you have it set, change 
groupShades={false}tonoShadeGrouping 
ColorPickerComponent
- rename the component and imports to 
ColorPicker - change the 
typeprop to a string instead of aColorPickerTypeobject (hint: use autocomplete to see all the possible values) - if you have it set, change 
groupShades={false}tonoShadeGrouping - if you have it set, replace 
includeWpBottomSpacing={false}withnoBottomSpacing 
Responsive
- check and remove all breakpoint labels you had set manually, they're now automatically rendered by the component
 
CompactResponsive
- rename the component and imports to 
Responsive - check and remove all breakpoint labels you had set manually, they're now automatically rendered by the component
 
CustomSelect
- this component has been split up into 4 more specific components
- if you had a CustomSelect without 
multipleand withoptions, replace it withSelect - if you had a CustomSelect without 
multipleand withloadOptions, replace it withMultiSelect - if you had a CustomSelect with 
multipleand withoptions, replace it withMultiSelect - if you had a CustomSelect with 
multipleand withloadOptions, replace it withAsyncMultiSelect 
 - if you had a CustomSelect without 
 - replace 
isClearablewithclearableif you had it set totrue - replace 
isSearchablewithnoSearchif you had it set tofalse, otherwise remove it - remove 
reFetchOnSearch, as it was removed - remove 
multiple(make sure you add the proper kind of Select!) - if you had an async select (with 
loadOptions), and hadsimpleValueset, you will need to find a slightly different solution, as this is not supported anymore 
LinkEditComponent
- ⚠️ props have changed here, it'll leave URL pickers broken if you forget to change them!
 - replace 
setAttributes,urlAttrName,isNewTabAttrName(if set) with anonChangecallback (see example below) - remove 
titlewithlabelif you want to keep it customized (you can also just remove it) - replace 
showNewTabOptionwithhideOpensInNewTaband invert its logic - you can now hide the anchor notice with 
hideAnchorNotice 
Example
Before
url={buttonUrl}
<LinkEditComponent
	url={buttonUrl}
	opensInNewTab={buttonIsNewTab}
	setAttributes={setAttributes}
	title={variableLabel}
	urlAttrName={getAttrKey('buttonUrl', attributes, manifest)}
	isNewTabAttrName={getAttrKey('buttonIsNewTab', attributes, manifest)}
	showNewTabOption={showButtonIsNewTab}
/>
After
<LinkEditComponent
	url={buttonUrl}
	opensInNewTab={buttonIsNewTab}
	hideOpensInNewTab={!showButtonIsNewTab} // Inverted logic!
	onChange={({ url, newTab, isAnchor }) => setAttributes({
		[getAttrKey('buttonUrl', attributes, manifest)]: url,
		[getAttrKey('buttonIsNewTab', attributes, manifest)]: newTab,
		[getAttrKey('buttonIsAnchor', attributes, manifest)]: isAnchor ?? false, // Optional, can replace a manual toggle (detects setting anchor links automatically).
	})}
/>
SimpleVerticalSingleSelect
- replace the component with 
OptionSelector - replace the 
optionsprop (one that returns an object) withvalue,onChangecallback and availableoptions(array) 
Example
Make sure your options have at least a label and a value!
Before
const sizeOptions = getOption('buttonSize', attributes, manifest).map(({ label, value, icon: iconName }) => {
		return {
			onClick: () => setAttributes({
				[getAttrKey('buttonSize', attributes, manifest)]: value,
				[getAttrKey('buttonIsLink', attributes, manifest)]: false
			}),
			label: label,
			isActive: buttonSize === value,
			icon: icons[iconName],
		};
	});
// ...
<SimpleVerticalSingleSelect
	label={<IconLabel icon={icons.size} label={__('Size', 'domain')} />}
	options={sizeOptions}
/>
After
<OptionSelector
	icon={icons.size}
	label={__('Size', 'domain')}
	options={getOption('buttonSize', attributes, manifest)}
	value={buttonSize}
	onChange={(value) => setAttributes({
		[getAttrKey('buttonSize', attributes, manifest)]: value,
		[getAttrKey('buttonIsLink', attributes, manifest)]: false
	})}
/>
OptionPicker
- replace with 
OptionSelector - add 
noBottomSpacingandborder='none' - add 
additionalContainerClass='es-p-1.25'to align it properly with other controls - remove the 
label 
Consider relocating your toolbar option to the options sidebar.
LinkToolbarButton
- replace with 
LinkEditComponent- follow the guide for that component for other prop replacements
 
 - you might need to place it in a
 
Example
Before
<LinkToolbarButton
	urlAttrName={getAttrKey('chevronUrl', attributes, manifest)}
	isNewTabAttrName={getAttrKey('chevronIsNewTab', attributes, manifest)}
	url={chevronUrl}
	opensInNewTab={chevronIsNewTab}
	setAttributes={setAttributes}
	title={__(ucfirst(componentName), 'domain')}
/>
After
import { ToolbarButton, ToolbarItem, } from '@wordpress/components';
import { PopoverWithTrigger } from '@eightshift/frontend-libs/scripts';
// ...
<ToolbarItem as='div'>
	<PopoverWithTrigger
		position='top right'
		contentClass='es-w-80 es-p-4'
		trigger={
			({ ref, setIsOpen, isOpen }) => (
				<ToolbarButton
					ref={ref}
					onClick={() => setIsOpen(!isOpen)}
					isPressed={chevronUrl?.length > 0}
					icon={icons.link}
				/>
			)
		}
	>
		<LinkEditComponent
			url={chevronUrl}
			opensInNewTab={chevronIsNewTab}
			onChange={({ url, newTab }) => setAttributes({
				[getAttrKey('chevronUrl', attributes, manifest)]: url,
				[getAttrKey('chevronIsNewTab', attributes, manifest)]: newTab,
			})}
			hideOpensInNewTab
			noBottomSpacing
		/>
	</PopoverWithTrigger>
</ToolbarItem>
InlineNotification
- rename the component and imports to 
Notification - change the 
typeprop to a string instead of aInlineNotificationTypeobject (hint: use autocomplete to see all the possible values) - replace 
removeBottomFieldSpacingwithnoBottomSpacing, if set - remove 
showContrastOutlineas it's not supported anymore 
SpacingSlider
- replace with 
ResponsiveSliderwith the config generator - remove deprecated props
 - if you had 
compensateForRemBase10, addmodifyInput={(v) => v * 10}
modifyOutput={(v) => v / 10} 
Example
Before
<SpacingSlider
	icon={icons.order}
	label={__('Order', 'domain')}
	attributeName='columnOrder'
	attributes={attributes}
	setAttributes={setAttributes}
	manifest={manifest}
	markSteps={2}
	hasInputField={false}
	hasValueDisplay
	valueDisplayFormat={(v) => !isNaN(v) && v > 0 ? v : '-'}
	showDisableButton
	disableWithUndefined
	isNumeric
/>
After
<ResponsiveSlider
	{...generateResponsiveSliderConfig({
		attributeName: 'columnOrder',
		attributes: attributes,
		manifest: manifest,
		setAttributes: setAttributes,
	})}
	icon={icons.order}
	label={__('Order', 'domain')}
/>
WidthOffsetRangeSlider
- wrap some of the deprecated options with a config generator
 
Example
Before
<WidthOffsetRangeSlider
	offsetAttributeName='columnOffset'
	widthAttributeName='columnWidth'
	manifest={manifest}
	attributes={attributes}
	setAttributes={setAttributes}
	showFullWidthToggle={false}
	includeGutters
	showOffsetAutoToggle
/>
After
<WidthOffsetRangeSlider
	{...generateWidthOffsetRangeSliderConfig({
		offsetAttributeName: 'columnOffset',
		widthAttributeName: 'columnWidth',
		attributes: attributes,
		manifest: manifest,
		setAttributes: (attr) => {
			// This is only required if you have "Auto" offset as an option, and the attributes are numeric, otherwise just pass setAttributes as-is.
			const newAttr = {};
			Object.entries(attr).forEach(([key, value]) => {
				if (value !== 'auto' && typeof value !== 'undefined') {
					newAttr[key] = parseInt(value);
					return;
				}
				newAttr[key] = value;
			});
			setAttributes(newAttr);
		},
		numOfColumns: 14,
		showOffsetAutoToggle: true,
		numericValues: true,
		showFullWidth: false,
	})}
/>
VisibilityToggleResponsive
- replace with 
ResponsiveToggleButtonand use the config generator 
Example
Before
<VisibilityToggleResponsive
	attributeName='columnHide'
	label={__('Visibility', 'redesign')}
	manifest={manifest}
	attributes={attributes}
	setAttributes={setAttributes}
/>
After
<ResponsiveToggleButton
	{...generateResponsiveToggleButtonConfig({
		attributeName: 'columnHide',
		manifest: manifest,
		attributes: attributes,
		setAttributes: setAttributes,
	})}
	label={__('Hide', 'domain')}
	icon={icons.hide}
/>
SimpleHorizontalSingleSelect
- rename the component and imports to 
OptionSelector - remove 
border='offset'if set, that's now the default - replace 
includeWpBottomSpacing={false}withnoBottomSpacing 
AlignmentToolbar
- replace with 
OptionSelector - add 
optionLabelsto transform just values into{label, value}(or{label, value, icon}) entries - add 
noBottomSpacing,additionalContainerClass='es-p-1.25', andborder='none'so it fits the toolbar better - remove 
labelandtype 
Example
Before
<AlignmentToolbar
	value={cardAlign}
	options={getOption('cardAlign', attributes, manifest)}
	label={sprintf(__('%s content align', 'domain'), manifestTitle)}
	onChange={(value) => setAttributes({ [getAttrKey('cardAlign', attributes, manifest)]: value })}
	type={AlignmentToolbarType.HORIZONTAL}
/>
After
<OptionSelector
	value={cardAlign}
	options={getOption('cardAlign', attributes, manifest)}
	onChange={(value) => setAttributes({ [getAttrKey('cardAlign', attributes, manifest)]: value })}
	optionLabels={getOption('cardAlign', attributes, manifest).map((v) => ({ label: ucfirst(v), icon: icons[`textAlign${ucfirst(v)}`] }))}
	additionalContainerClass='es-p-1.25'
	noBottomSpacing
	border='none'
	iconOnly
/>
HeadingLevel
- replace with 
OptionSelector - change 
selectedLeveltovalue - add 
noBottomSpacing,additionalContainerClass='es-p-1.25', andborder='none'so it fits the toolbar better - add 
optionswith all the available heading level options (see After in the example below) - if you want the options to look a bit better, you can add 
additionalButtonClass, just like in the After example below 
Example
Before
<HeadingLevel
	selectedLevel={typographyLevel}
	onChange={(value) => setAttributes({ [getAttrKey('typographyLevel', attributes, manifest)]: value })}
/>
After
<OptionSelector
	value={typographyLevel}
	onChange={(value) => setAttributes({ [getAttrKey('typographyLevel', attributes, manifest)]: value })}
	additionalContainerClass='es-p-1.25'
	noBottomSpacing
	border='none'
	options={[
		{ label: 'H1', tooltip: __('Heading 1', 'domain'), value: 1 },
		{ label: 'H2', tooltip: __('Heading 2', 'domain'), value: 2 },
		{ label: 'H3', tooltip: __('Heading 3', 'domain'), value: 3 },
		{ label: 'H4', tooltip: __('Heading 4', 'domain'), value: 4 },
		{ label: 'H5', tooltip: __('Heading 5', 'domain'), value: 5 },
		{ label: 'H6', tooltip: __('Heading 6', 'domain'), value: 6 },
	]}
	additionalButtonClass='es-button-square-36 es-text-4 es-font-weight-300'
/>
CustomSlider
- rename the component and imports to 
OptionSelector - you might want to remove 
marksif not very specific, as an auto-generator for marks is now included 
SimpleRepeater / SimpleRepeaterItem
- rename the component and imports to 
Repeater/RepeaterItem