Developer Quickstart

Introduction

When approaching Sky UI, both in Figma and React we opt for a composition-centric approach. Our UI stack is composed of the following levels:

  • Foundations (tokens) - These are the core values that drive the system such as colour, typography and spacing.
  • Components - Our smallest units of UI, these are the building blocks of the system.
  • Patterns - Our precomposed UI patterns, typically patterns are more specialised.
  • Core - A set of system modifier and helper function.
  • Animation – A library of components and hooks for building animations.

Sky UI Core

Sky UI Core offers a set of UI components, theme variables (detailed in the Foundations section), system modifiers, and useful hooks (found in the Core section). The Patterns section showcases reusable UI patterns built using Sky UI components.

Installation

You will need an npm token to be able to download Sky UI Core.

If your repo does not already have a npm token, consult a Tech Lead or a Principal Engineer who can generate a read-only token for you.

yarn add @sky-uk/ui-core

Global Style Reset

To ensure Sky UI performs correctly on all browsers make sure to include the GlobalReset in your app, this should be added early in your application:

<App>
<GlobalReset />
//...
</App>

Basic Usage

Sky UI Core supplies a number of useful components out of the box, in this example we use the Button component in our custom UI pattern and set the props we require for our use case:

import React from 'react';
import { Button } from '@sky-uk/ui-core';
export const MyPattern = () => (
<Button $variant="primary" $marginTop={4}>
Button
</Button>
);

You can also use Sky UI Core theme and system helpers and utilities (see Core) to rapidly build out new bespoke components:

import styled from 'styled-components';
import { color, applyModifierProps } from '@sky-uk/ui-core';
export const MyComponent = styled.div`
background-color: ${color(primary)};
${applyModifierProps({
system: ['margin', 'padding', 'width'];
})}
`;

Customising Components

You can utilise the default functionality of Sky UI components, modify them, or use them to construct custom components. Since Sky UI components are built using styled-components, we strongly suggest referring to the Styled-Components guide for insights on customising Sky UI components.

Using Theme Values

Whenever possible, components should align with approved theme values. This example sets the primary blue brand colour as the element's background. Theme values are exposed via functions such as color(), spacing() and breakpoint().

import { styled } from 'styled-components';
import { color } from '@sky-uk/ui-core';
const MyComponent = styled.div`
background-color: ${color('primary')};
`;

Styling With Props

To prevent styling props from being rendered directly to the DOM element, we use transient props. These require prefixing styling props with $. For a deeper dive into transient props, refer to the Styled Components documentation.

The transient prop convention should only be applied to stylistic props, any passed/data props or native HTML attributes should be used as usual.

The following example illustrates the conjunction of styling props with non-stylistic props:

<MyComponent $marginBottom={4} componentData={data} aria-label="My Component" />

Using Basic Modifiers

The component has access to all props that are passed to it by the consumer. This can be used to allow consumers to modify your component's styles via props:

// Implemenation
import { styled } from 'styled-components';
const MyComponent = styled.div`
background-color: ${({ $color }) => ($color === 'blue' ? 'blue' : 'red')};
`;
// Consumption
<MyComponent $color="blue">Hello</MyComponent>;

Using System Modifiers

@sky-uk/ui-core comes with several ready made modifiers built in. They can be applied to your component using the applyModifierProps() function.

This example demonstrates how to create a basic component using the applyModifierProps function to add the color and margin system modifiers:

import styled, { css } from 'styled-components';
import { applyModifierProps } from '@sky-uk/ui-core';
const MyComponent = styled.div`
background-color: red;
${applyModifierProps({
system: ['color', 'margin'],
})};
`;

This gives us a shorthand to build out adaptable components with responsive prop support:

<MyComponent $color="primary" $marginBottom={{ xs: 4, md: 6 }} />

All modfiers added via the applyModifierProps will be responsive.

Some of the system modifiers can handle multiple prop names. For example the margin system modifier also adds support for props like $marginTop, $marginBottom, and so on.

Core - System for more information about the reusable system modifiers.

Applying Custom Modifiers

You can also apply bespoke modifiers via the applyModifierProps function like so:

import styled, { css } from 'styled-components';
import { applyModifierProps } from '@sky-uk/ui-core';
const $appearance = {
red: css`
background-color: red;
`,
blue: css`
background-color: blue;
`,
};
const MyComponent = styled.div`
${applyModifierProps({
modifiers: { $appearance },
})};
`;

Here we define an object named $appearance and add it to our modifiers. If the consumer provides an $appearance prop, the styles from any matching key within the object will be returned.

The modifier definition does not need to be an object. It can also handle boolean values and functions.

This is an example of a function modifier:

import styled, { css } from 'styled-components';
import { applyModifierProps } from '@sky-uk/ui-core';
const $color = value => css`
background-color: ${value};
`;
const MyComponent = styled.div`
${applyModifierProps({
modifiers: { $color },
})};
`;

applyModifierProps() automatically generates responsive props allowing values to be assigned for different breakpoints:

<MyComponent $color={{ xs: 'red', lg: 'blue' }}>Hello!</MyComponent>

When to use applyModifierProps vs CSS

System modifiers should be used to aid composition and extend functionality to the consumer. They must not replace media queries for non-consumer controlled styles.

Don't do this:

const Wrapper = styled.div`
${applyModifierProps({
system: ['padding', 'width'],
})}
`;
const Component = () => (
<Wrapper $paddingX={{ xs: 4, lg: 5 }} $paddingY={5} $width="100vw">
// ... stuff
</Wrapper>
);

In this example, the system modifiers are unnecessary as they are all hardcoded within the component.

Do this instead:

const Wrapper = styled.div`
padding: ${spacing(4)};
width: 100vw;
@media (min-width: ${breakpoint('lg')}) {
padding: ${spacing(5)};
}
`;
const Component = () => <Wrapper>// ... stuff</Wrapper>;

This approach is better for performance. If we decided to spread props on Wrapper, we wouldn't risk exposing unwanted system modifiers to the end user.

As a general rule, only use applyModifierProps() for props the consumer needs access to. Hardcoded props should be performed statically.

Composition

When building out UI patterns, we prioritise composition rather than prop driven components.

Take a custom Hero component, for example, we could build this to be prop driven:

<Hero
heading="Hero heading"
bgAsset="image-url"
cta={{
text: 'click me',
href: 'link-url',
}}
//...
/>

However, this approach doesn't handle small design variation well. The more options and permutations we require, the more complex the API and the codebase becomes. This is especially true when it comes to running A/B tests and in similar use cases.

Instead, we create our components to leverage composability:

<Hero>
<Hero.Asset>{image ? <Image src={src} /> : <Video src={src} />}</Hero.Asset>
<Hero.Content>
<Text as="h2">Hero heading</Text>
<CustomComponent />
</Hero.Content>
<Hero.Actions>
<Button href="link-url">click me</Button>
</Hero.Actions>
</Hero>

While the set up may require a little more work by the user, it gives a lot more flexibility to compose different elements as needed while maintaining some high-level rules and principles of anatomy.

If the heading needs to be changed to an h1 or if additional custom UI elements are required, the consumer has the flexibility to do so. It also provides more intuitive control over the inclusivity markup of elements in the Hero. Additionally, it aids in managing the behavior of buttons, overlays, and focus.