Skip to main content

Adapt a component

Overview

Adapting a component means to use the component "directly". This includes using the same name and purpose for it. The obvious example would be using the Button provided by kickstartDS for the Button as part of your own Design System. It typically doesn't make a lot of sense to find a different name for this component, because the name is fitting and well-known.

the process of changing to suit different conditions
https://dictionary.cambridge.org/dictionary/english/adaptation

The word "directly" above is set in quotes, because even though we re-use the component with the same intent, we will still want to reduce the offered options (component properties) to better suit our needs. A simple component API helps in creating a shared understanding of what a component is, and does.

kickstartDS component properties

Components as delivered by kickstartDS modules (like @kickstartDS/base for the Button) have to support a wide range of use cases, as they get used to build a multitude of different, but custom, Button implementations in the end.

This means reducing the set of properties, given by the component API of a kickstartDS component, to your needs is explicitly something that should be done! You still benefit by the broad, underlying foundation giving you flexibility when further adapting your own version of it. For example when adding a variant including an Icon, which was not part of your Button before.

For a detailed example of adapting the Button component, have a look at our component example guide describing just that!

To learn more about the different processes available to you when creating a component with kickstartDS, have a look at the overview page of this section.

Adaptation process

There's one big assumption made when choosing the adaptation process: you've already identified a component you'd like to use... and it already offers all the options you'll need. Let's call this component the base component.

This greatly simplifies finding a component definition, as it means simply selecting props you'd like to use for yourself, from a well-defined set.

Adaptation process as a base line

Each of the other ways of adding a component to your Design System based on kickstartDS builds upon this adapdation process.

Which is to say, you'll always want to reduce the props of kickstartDS components used as a base, as described here. But sometimes that may not be enough. You might need a slightly different version of the component, or its props (learn more in our guide "Customize a component", or view "Customize Headline component" as an example), or you may want to add something completely new to it (learn more in our guide "Extend a component", or view "Extend Section component" as an example).

If you don't have component in mind yet, you're probably better served by our guide "Create a component". In that guide, you'll start off without a specific component in mind. This means defining a structure (someting you probably have an idea about already) first, and then mapping that structure to a fitting component second.

We also have an example for this with "Create Teaser Card component".

If you're still unsure, then maybe you're still missing a clear picture on what your components should look like. In that case, you should probably take a step back first, and maybe start a Design System Initiative to narrow down on what components you'll really need.

There are two main steps in adapting a component:

  1. Component definition, and
  2. Component creation

Let's see what they're all about!

1. Component Definition

The adaptation process starts by defining a component API. As touched on before, this mainly means selecting a set of props from the pool of props available through your selected base component.

We'll use the Button component throughout this guide to illustrate concepts. This will not be an exhaustive example, though. For that have a look at our guide "Adapt Button component".

Purpose

For the adaptation process the main purpose of a component is already defined by the selected base component. Nontheless, the purpose for even a well-known type of component, like the Button, can vary from one Design System to the next. So it helps to think about the specific purpose the selected base component will serve in your Design System.

As a hypothetical: you might decide to really only use components looking like a Button for elements the user directly interacts with... but not for links. This would be a big part of the components purpose, and thus should inform your property selection and naming process.

Structure

Before we start implementing, we'll want to define a first rough draft of our component API. Defining a name, a small description and a rough type, per property, goes a long way in keeping the focus on the core of your component.

Let's keep using the Button as an example. We're starting with the following properties with that one:

PropertyTypeDescription
label *stringText used on button
variant *enumChoose one of the styles from the list
invertedbooleanIf the button should be inverted
size *enumChoose a size between small, medium and large
hrefstringLink used for button
iconBeforeobjectIcon identifier for icon before the button text
iconAfterobjectIcon identifier for icon after the button text
fillAnimationbooleanAdd fill animation on hover
iconAnimationbooleanAdd icon animation on hover
typestringSelect the type attribute for the button
valuestringDefine a value attribute for the button
namestringDefine a name attribute for the button
disabledbooleanSet the disabled attribute for the button
newTabbooleanOpen link in new Tab
classNamestringAdd additional css classes that should be applied to the button
componentstringOptional custom component identifier

Fields that should be required are marked with a *.

For the detailed documentation have a look at the Button in our Storybook here:
https://www.kickstartds.com/storybook/?path=/docs/base-button--solid

One potential set of props, it's also the one used in our guide "Adapt Button component" if you're wondering, would be the following:

PropertyTypeDescription
label *stringText content to display inside the button
targetstringTarget that should be linked, makes the button behave like a link semantically when set
variantenumVariant of button to be used
sizeenumSize of button to use
disabledbooleanWhether the button should be disabled

We took label, variant, size and disabled directly, and renamed href to target for our version of the Button. Required fields are marked with a * again.

In our hypothetical above, we would probably drop the option of specifying href or target altogether.

2. Component Creation

In the second and final step we'll get to actually create our component. We'll encode the component API by creating its JSON Schema, and create a React template matching our selected properties to the kickstartDS base component.

JSON Schema definition

We establish the structure of components by creating a JSON Schema for them, defining their component API in code.

For an abridged version of that process, have a look at the Button again:

Original Button

This is the "full" button component API / JSON Schema. We've just reduced the property definitions.

Select from available props

Let's highlight the ones we've identified when thinking about our component structure before.

Create your component API

Subsequently we add exactly those fields to our own components component API / JSON Schema...

Renaming props

... and we can even rename props in the process.

Add required fields

And finally, set all the fields identified as required.

Finished component JSON Schema

The finished component definition in all its glory.

@kickstartds/button.schema.json

_26
{
_26
"$schema": "http://json-schema.org/draft-07/schema#",
_26
"$id": "http://schema.kickstartds.com/base/button.schema.json",
_26
"title": "Button",
_26
"description": "Component to display links and call-to-actions",
_26
"type": "object",
_26
"properties": {
_26
"label": { ... },
_26
"variant": { ... },
_26
"inverted": { ... },
_26
"size": { ... },
_26
"href": { ... },
_26
"iconBefore": { ... },
_26
"iconAfter": { ... },
_26
"fillAnimation": { ... },
_26
"iconAnimation": { ... },
_26
"type": { ... },
_26
"value": { ... },
_26
"name": { ... },
_26
"disabled": { ... },
_26
"newTab": { ... },
_26
"className": { ... },
_26
"component": { ... }
_26
},
_26
"additionalProperties": false
_26
}

For the full version of adapting a Button have a look at our guide "Adapt Button component".

React template

Now that our JSON Schema is defined, we'll automatically get matching TypeScript types for our component. We use those, combined with the types already included with the kickstartDS base component, to quickly hook up our set of properties to the original component. Using auto-complete, and TypeScript telling us about required properties in the base component, this is done in light speed!

To learn more about the tooling that create those types for you, and how to hook it up, see part four of our "Create your Design System" guide.

Let's continue showcasing this process using our Button, creating the component template:

Necessary imports

The main imports here are the kickstartDS base component and our own components TypeScript types.

Add correct type to component

We need to type our React component to use our JSON Schema, while also making sure native HTML attributes and React refs are passed correctly. For the Button this means also including HTMLAnchorElement | HTMLButtonElement and using forwardRef.

Doing this allows users of your component to enjoy having the same auto-complete and safety when working with your Design System.

Add parameters to component

Next we add all our components defined properties to its function signature. For properties having a default defined in your component API we add that default here, too.

As we also want to pass through all the props not explicitly managed by us we sponge up ...props.

And in the case of the Button, we also have to add the ref added through forwardRef.

Destructure additional props

Coming to our component JSX, we start by passing down (destructuring) the props we're carrying through first. This ensures properties defined in our component API will always take precedence, because they're added after the general props.

Glue component API to base component 1/3

The simplest cases of connecting props to the base component is when the name of the property is taken from the base component. We can just directly pass those without much thought.

Glue component API to base component 2/3

In some cases we might have changed the name of a prop. In that instance, we just to have to wire up the renamed property to the originally named base components property.

Glue component API to base component 3/3

Finally there might be properties where the naming of the actual values was changed, for example when adapting enum typed properties.

We'll have to simply rewire the values in that case.

Finalize component JSX by adding the ref

This will be optional, and depend on the element being handled. If you've included a forwardRef before, you'll have to pass that ref down to the kickstartDS base component.

We'll have to simply rewire the values in that case.

Adding a Provider

The final part of creating our React component is adding a component Provider for it. As we've adapted a component here, we'll want to make sure that every time another component includes the base component, our own version of it gets used instead.

src/components/button/ButtonComponent.tsx

_38
import { HTMLAttributes, forwardRef, FC, PropsWithChildren } from "react";
_38
import {
_38
ButtonContextDefault,
_38
ButtonContext,
_38
} from "@kickstartds/base/lib/button";
_38
import { ButtonProps } from "./ButtonProps";
_38
_38
export const Button = forwardRef<
_38
HTMLAnchorElement | HTMLButtonElement,
_38
ButtonProps & HTMLAttributes<HTMLElement>
_38
>(({
_38
label,
_38
target,
_38
size = "medium",
_38
variant = "secondary",
_38
disabled = false,
_38
...props
_38
}, ref) => (
_38
<ButtonContextDefault
_38
{...props}
_38
href={target}
_38
label={label}
_38
size={size}
_38
variant={
_38
variant === 'primary'
_38
? 'solid'
_38
: variant === 'secondary'
_38
? 'outline'
_38
: 'clear'
_38
}
_38
disabled={disabled}
_38
ref={ref}
_38
/>
_38
));
_38
_38
export const ButtonProvider: FC<PropsWithChildren<any>> = (props) => (
_38
<ButtonContext.Provider {...props} value={Button} />
_38
);

If you're wondering what that Provider we've added last is all about, think about it like this:
There may be other components you've built, that themselves use the base Button component by default. For example we might have our own TeaserCard component, based on the kickstartDS TeaserBox which includes a Button.

As a means to not having to go through every combination of those component now, making sure our Button actually gets used, you can just change the default Button rendered by adding a single Provider once, instead.

Learn more about Providers and React Context in[our dedicated page about them.
Or look at our "Create your Design System" guide, where we add the general setup for Providers. That one also includes some more details on this!

Visual Studio Code component property quick-fix

Visual Studio Code has a great feature aiding in this workflow, with React components that include TypeScript types... like kickstartDS components do. When adding a "bare" component without props to your template, Visual Studio Code will offer you the option to Add missing attributes.

This will automatically create all required options for your component. Now you just have to connect your own props to those, while hard-coding the ones you don't plan on exposing as part of your components component API.

Just hover the squiggly, red line that should be decorating your component, and choose Quick Fix..., to get to that option (alternatively put your cursor on the component tag and hit Ctrl+.).

Technical debt added

This way of creating components adds minimal technical debt to your Design System. Not much has been changed around, we just add a small layer on top of the original kickstartDS base component.

Relevant underlying changes you'll have to look out for:

  • changes to the base components component API
  • removal of the base component

You're immune to underlying changes to:

  • the components template (both React, and the resulting HTML)
  • the design and layout (changes to CSS, SCSS and Design & Component Token)

In the case of a changed component API, you should have a look at the corresponding CHANGELOG.md and potential notes in our matching migration guide. You'll probably just need to add a newly added field to your React template, and potentially your own component API if you want to use it. If a field was changed, that might also necessitate some adaption of your own version. Finally a removed field you're actually using would mean adding additional customization to regain that functionality. Have a look at our Headline example guide to see how you'd add your own, new property!

Learn more about what we mean by technical debt here on the overview page of this section.

Examples

We continuously expand our component example guides, below we've collected the ones acting as a good sample of the adaptation process.

Adapt Button component

In this example component guide we adapt the Button component (as part of the @kickstartDS/base module) to use it for buttons in our own Design System. We drastically reduce the options used, and offered, in our own adaptation. We remove all options that include having an Icon displayed as part of the Button, for example.

This is what the result looks like:


_10