Follow our main guide to learn how you can "Create your Design System" in just 5 steps: Open guide
Extend Section component
This guided example shows how you'd add components to your Design System that use a kickstartDS base component pretty directly. But unlike just adapting a component, extending a component also involves adding something to it, or changing the way certain things work under the hood, by composing multiple kickstartDS base components (whereas customizing a component does that by adding changes to the customized components React template). This expands possible applications of existing kickstartDS components greatly.
Even while using the component rather directly from kickstartDS, you'll want to find the correct set of properties for your own use case. Components in kickstartDS come equipped with properties for a wide range of possible use cases, so it makes sense to reduce those to the ones you really need... to make components easier to understand, use and reason about!
We call this type of workflow Extension. Learn more about it in our dedicated guide about it. If you're unsure about something, have a look over there. We go into more background detail there about what we're doing here.
Not touching the actual markup generated by components let's us get by without adding any custom styling (CSS / SCSS) to it. We simply reuse the already existing Design Token and component structure.
This guide assumes that you already have a working Design System, that is based on kickstartDS, running.
If that's not the case, follow our Create your Design System guide.
We've found that we'd love to use the existing kickstartDSSection, but it's not quite flexible enough for our taste. We'd like to add more call-to-actions to our page, which mostly consists of such Sections, and don't need as much flexibility for the included headline.
We take width, gutter, mode, content, spaceBefore, spaceAfter and inverted directly from the Section, and rename background to style for our version of it. And crucially we add our own property ctas, to hold call-to-actions, into the mix, while reducing the complexity of headline significantly... from mapping to all of the Headline properties, to just a single string type prop setting its content.
We also keep the name Section, as it fits our use case well enough already.
We like to colocate components. This means to have all involved files next to each other in the same folder; the template (.jsx / .tsx), potential CSS / SASS (.css / .scss), JavaScript (.js / .ts), our JSON Schema component definition (.schema.json), and so on.
So we start by creating the directory src/components/section, from our Design System repository root:
_10
mkdir -p src/components/section
This is the folder we'll add new files to in the coming few paragraphs.
We start by adding a title, description and $id attribute. The correct $id depends on your Design System configuration. We'll assume you've created components before, living under the schema prefix http://schema.mydesignsystem.com.
The headline field is a straight-forward string type properties, so we just document it a bit!
We do mark it by setting format to markdown, though, to enable some light RTE-like formatting options of the rendered text later on.
We add a field content of type array´. Let's assume we have three components that can be used as content for the section. We reference each one using anyOfand$ref`.
We specify the array items type, adding an object with properties label and target to it, both of simple string types (with target having format set to uri to enable resource-like behaviour).
We start by adding a title, description and $id attribute. The correct $id depends on your Design System configuration. We'll assume you've created components before, living under the schema prefix http://schema.mydesignsystem.com.
The headline field is a straight-forward string type properties, so we just document it a bit!
We do mark it by setting format to markdown, though, to enable some light RTE-like formatting options of the rendered text later on.
We add a field content of type array´. Let's assume we have three components that can be used as content for the section. We reference each one using anyOfand$ref`.
We specify the array items type, adding an object with properties label and target to it, both of simple string types (with target having format set to uri to enable resource-like behaviour).
"description": "Amount of spacing before the section",
_104
"enum": ["default", "small", "none"],
_104
"default": "default"
_104
},
_104
"spaceAfter": {
_104
"type": "string",
_104
"title": "Space After",
_104
"description": "Amount of spacing after the section",
_104
"enum": ["default", "small", "none"],
_104
"default": "default"
_104
},
_104
"inverted": {
_104
"type": "boolean",
_104
"title": "Inverted",
_104
"description": "Whether to invert the section",
_104
"default": false
_104
},
_104
"ctas": {
_104
"type": "array",
_104
"title": "Call to actions",
_104
"description": "Add Call to actions to the end of the section",
_104
"items": {
_104
"type": "object",
_104
"properties": {
_104
"label": {
_104
"type": "string",
_104
"title": "Label",
_104
"description": "Label for the Call to action"
_104
},
_104
"target": {
_104
"type": "string",
_104
"title": "Target",
_104
"description": "Target for the Call to action",
_104
"format": "uri"
_104
}
_104
},
_104
"required": ["label", "target"]
_104
}
_104
}
_104
},
_104
"required": []
_104
}
This concludes creating the JSON Schema. When running the schema generation in our Design System again, we should now automatically end up with a corresponding type definition to be used in creation of the template in the next step:
src/components/section/SectionProps.ts
src/components/section/section.schema.json
_176
/* eslint-disable */
_176
/**
_176
* This file was automatically generated by json-schema-to-typescript.
_176
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
_176
* and run `yarn run schema` to regenerate this file.
export type SpaceAfter = "minimum" | "small" | "large";
_176
/\*\*
_176
- Headline for the teaser card
_176
\*/
_176
export type Headline2 = string;
_176
/\*\*
_176
- Body text for the teaser card
_176
\*/
_176
export type Text1 = string;
_176
/\*\*
_176
- Target that should be linked
_176
\*/
_176
export type Target1 = string;
_176
/\*\*
_176
- Image to display as cover
_176
\*/
_176
export type Image = string;
_176
/\*\*
_176
- Whether to invert the card
_176
\*/
_176
export type InvertCard = boolean;
_176
/\*\*
_176
- Allowed content for the section
_176
\*/
_176
export type Content = (Button | Headline1 | TeaserCard)[];
_176
/\*\*
_176
- Style of background
_176
\*/
_176
export type Style1 = "default" | "accent" | "bold";
_176
/\*\*
_176
- Amount of spacing before the section
_176
\*/
_176
export type SpaceBefore = "default" | "small" | "none";
_176
/\*\*
_176
- Amount of spacing after the section
_176
\*/
_176
export type SpaceAfter1 = "default" | "small" | "none";
_176
/\*\*
_176
- Whether to invert the section
_176
\*/
_176
export type Inverted = boolean;
_176
/\*\*
_176
- Label for the Call to action
_176
\*/
_176
export type Label1 = string;
_176
/\*\*
_176
- Target for the Call to action
_176
\*/
_176
export type Target2 = string;
_176
/\*\*
_176
- Add Call to actions to the end of the section
_176
\*/
_176
export type CallToActions = {
_176
label: Label1;
_176
target: Target2;
_176
[k: string]: unknown;
_176
}[];
_176
_176
/\*\*
_176
_176
- Component used to layout components into pages
_176
\*/
_176
export interface SectionProps {
_176
headline?: Headline;
_176
width?: Width;
_176
gutter?: Gutter;
_176
mode?: Mode;
_176
content?: Content;
_176
style?: Style1;
_176
spaceBefore?: SpaceBefore;
_176
spaceAfter?: SpaceAfter1;
_176
inverted?: Inverted;
_176
ctas?: CallToActions;
_176
[k: string]: unknown;
_176
}
_176
/\*\*
_176
- Component used for user interaction
_176
\*/
_176
export interface Button {
_176
label: Label;
_176
target?: Target;
_176
variant?: Variant;
_176
size?: Size;
_176
disabled?: Disabled;
_176
[k: string]: unknown;
_176
}
_176
/\*\*
_176
- Component used for headlines
_176
\*/
_176
export interface Headline1 {
_176
text: Text;
_176
sub?: Sub;
_176
switchOrder?: SwitchOrder;
_176
level: Level;
_176
style?: Style;
_176
spaceAfter?: SpaceAfter;
_176
[k: string]: unknown;
_176
}
_176
/\*\*
_176
- Component used to tease content
_176
\*/
_176
export interface TeaserCard {
_176
headline: Headline2;
_176
text: Text1;
_176
target: Target1;
_176
image?: Image;
_176
inverted?: InvertCard;
_176
[k: string]: unknown;
_176
}
How your schema generation is started might change depending on your setup. If you've followed our "Create your Design System" guide before, or want to add it like we do, follow this section of it closely.
As the final step for this example, we'll add the template. This will be a purely functional React component, mapping our component structure (as defined in the JSON Schema) to the original component we're basing our work off of; the kickstartDSStorytelling component.
Import and add generated props from SectionProps.ts. Generated by our JSON Schema, these guarantee you're matching your expected component structure while implementing. In combination with TypeScript this enables auto-complete and auto-fix for even better DX! (see here, at the very end of that section, for more details)
We also add HTMLAttributes<HTMLElement> to the type signature for the props that we'll pass through to the native HTML element underneath.
We also need to add our own properties, so we'll destructure props. We add our default values here, too. We'll just pass through everything HTMLAttributes related!
Now we'll import and add the kickstartDSSection component. To start, we'll use the hard-coded properties of the Content BoxesSection variant from our kickstartDS Design System.
We'll omit the child components added there (ContentBoxes), as they only exist for illustrative purposes inside that Storybook. We will just pass children through to the original Section, later.
We remove all of the unneeded stuff, as there are a bunch of properties that are completely optional, mainly those having their values undefined or null in the copied JSX, or ones which just state the default value of that property anyway. Those can be freely removed.
We now import the component we want to use to display the call-to-actions, in this case a Button included in our own Design System. We also add a second Section (losing its headline) to hold our Buttons, and connect those to cta.label. We choose the variant of Button by index, first equals primary, second means secondary and all others are tertiary.
src/components/section/SectionComponent.tsx
_51
import { HTMLAttributes, FC } from "react";
_51
_51
import {
_51
SectionContextDefault,
_51
} from "@kickstartds/base/lib/section";
_51
_51
import { SectionProps } from "./SectionProps";
_51
import { Button } from "../button/ButtonComponent";
We then connect the props as defined in our component API that are directly taken from the underlying kickstartDS base component by just passing them through. We also destructure props first, so our own properties take precedence when set. We add it to the first Section, that will always be rendered.
src/components/section/SectionComponent.tsx
_56
import { HTMLAttributes, FC } from "react";
_56
_56
import { SectionContextDefault } from '@kickstartds/base/lib/section';
_56
_56
import { SectionProps } from './SectionProps';
_56
import { Button } from '../button/ButtonComponent';
As a final step, we make sure the spacing between the two Sections is optimized, so it will look seamless later on. We also hard code some options of the second Section.
src/components/section/SectionComponent.tsx
_56
import { HTMLAttributes, FC } from "react";
_56
_56
import { SectionContextDefault } from '@kickstartds/base/lib/section';
_56
_56
import { SectionProps } from './SectionProps';
_56
import { Button } from '../button/ButtonComponent';
Import and add generated props from SectionProps.ts. Generated by our JSON Schema, these guarantee you're matching your expected component structure while implementing. In combination with TypeScript this enables auto-complete and auto-fix for even better DX! (see here, at the very end of that section, for more details)
We also add HTMLAttributes<HTMLElement> to the type signature for the props that we'll pass through to the native HTML element underneath.
We also need to add our own properties, so we'll destructure props. We add our default values here, too. We'll just pass through everything HTMLAttributes related!
Now we'll import and add the kickstartDSSection component. To start, we'll use the hard-coded properties of the Content BoxesSection variant from our kickstartDS Design System.
We'll omit the child components added there (ContentBoxes), as they only exist for illustrative purposes inside that Storybook. We will just pass children through to the original Section, later.
We remove all of the unneeded stuff, as there are a bunch of properties that are completely optional, mainly those having their values undefined or null in the copied JSX, or ones which just state the default value of that property anyway. Those can be freely removed.
We now import the component we want to use to display the call-to-actions, in this case a Button included in our own Design System. We also add a second Section (losing its headline) to hold our Buttons, and connect those to cta.label. We choose the variant of Button by index, first equals primary, second means secondary and all others are tertiary.
We then connect the props as defined in our component API that are directly taken from the underlying kickstartDS base component by just passing them through. We also destructure props first, so our own properties take precedence when set. We add it to the first Section, that will always be rendered.
As a final step, we make sure the spacing between the two Sections is optimized, so it will look seamless later on. We also hard code some options of the second Section.
To complete the template we add the SectionProvider to our src/components/Providers.jsx:
src/components/Providers.jsx
_14
import { ButtonProvider } from "./button/ButtonComponent";
_14
import { SectionProvider } from "./section/SectionComponent";
_14
import { TeaserBoxProvider } from "./teaser-card/TeaserCardComponent";
_14
import { HeadlineProvider } from "./headline/HeadlineComponent";
_14
_14
export default (props) => (
_14
<ButtonProvider>
_14
<HeadlineProvider>
_14
<SectionProvider>
_14
<TeaserBoxProvider {...props} />
_14
</SectionProvider>
_14
</HeadlineProvider>
_14
</ButtonProvider>
_14
);
This concludes the creation of our new Section component. It's now ready to be used inside your Design System, and available to your down stream consumers... hopefully efficiently closing a gap for them!
If you're using Storybook, you can follow this part of the example to get all the integration goodness possible with kickstartDS!
Storybook setup
This guide assumes you're using a set up like described in our Create your
Design System guide! Be sure to adapt commands and
configuration to your use accordingly, when following this part!
Add the following file to your src/components/section folder:
We do this by binding to our Template, and use pack to convert all deep JSON args to flat (. delimited) keys and values. This is the format your Storybook Controls get generated off.
src/components/section/Section.stories.jsx
_65
import merge from "deepmerge";
_65
import {
_65
pack,
_65
unpack,
_65
getArgsShared,
_65
} from "@kickstartds/core/lib/storybook";
_65
import sectionStories from "@kickstartds/base/lib/section/section.stories";
_65
import TeaserCardStory, {
_65
CardWithImage,
_65
} from "../teaser-card/TeaserCard.stories";
_65
import schema from "./section.schema.dereffed.json";