Styling Components in Code

A quick guide to working with CSS and styling in general.

Fundamentals

At a base level, @vitality-ds/components uses a CSS-in-JS library (called Stitches) that should be able to handle all of your styling needs.

You should never need to attach external .scss files to the components, nor add complex loaders to webpack configurations.

Vitality exports a styled function that allows you to create your own components that "hook into" the theme to allow your styling to pull values from that central place. Their API is similar to Styled-Components or Emotion.

Most of the time, you should be able to create most of your features with pre-existing components.

Key Features of Vitality

  1. Vitality uses a CSS-in-JS library called Stitches. The core differentiators of Stitches are near-zero runtime, multi-variant support, server-side rendering (SSR) and a best-in-class developer experience.
  2. Built with TypeScript. As most styling customisations should be pulled from a central theme, Stitches allows Vitality to provide handy intellisense features and compile-time feedback.
  3. You don't need CSS loaders or preprocessors.
  4. Compose your own components using the styled function exported from @vitality-ds/system.
Aim For
  • Try to use ready-made components whenever possible
  • Create new, product-specific components using the styled() API
  • Use variants to configure pre-determined variants of a component

The styled function

This is the main way of creating and styling your own components. The general concept is to create a styled('foo', {}) and pass the styles as the second parameter.

Passing a Vitality component to styled(), may result in unexpected behaviours due to the fact that most Vitality components omit the style and className props. When creating custom components, consider using only native HTML elements if you need full flexibility.

import {
  styled,
  colorUseCases,
  getColorScaleValueByUseCase,
} from "@vitality-ds/system";

const { backgrounds } = colorUseCases;

const Status = styled("div", {
  backgroundColor: getColorScaleValueByUseCase(
    "primary",
    backgrounds.uiElement_solid
  ),
  padding: "$lg",
});

<Status />;

getColorScaleValueByUseCase()

This helper function can be used to support you in choosing an appropriate color value for styling. Essentially this function allows you to specify which "tint" of color you want (primary, accent, success etc) and which use case it's for.

backgroundColor: getColorScaleValueByUseCase(
  "primary", // the tint
  backgrounds.uiElement_solid // define the use case
);

Similar to Emotion/Styled-Components

The styled function has an API that is similar to Emotion or Styled-Components, but with a few differences. You can read more about the key differences between Emotion or Styled-Components on Stitches' blog.

Read more about the styled() function.

Add Variants

Use variants to create styles for variations of the component. You can then pass them in as props to your component.

import {
  styled,
  colorUseCases,
  getColorScaleValueByUseCase,
} from "@vitality-ds/system";

const { backgrounds, text } = colorUseCases;

const Status = styled('div', {
  backgroundColor: getColorScaleValueByUseCase(
    "primary",
    backgrounds.uiElementSolid
  ),
  color: getColorScaleValueByUseCase(
    "neutral",
    text.hiContrast
  ),
  padding:'$lg',

  // Variants are a powerful API
  variants: {
    state: {
      success: {
        backgroundColor: getColorScaleValueByUseCase(
          "success",
          backgrounds.uiElementSolid
        ),
      }
      critical: {
        backgroundColor: getColorScaleValueByUseCase(
          "error",
          backgrounds.uiElementSolid
        ),
      }
    }
  }
});

<Status />;
<Status state="success" />;
<Status state="error" />;
<Status state={state.error && 'error'} />;

Read more about about variants.

Complex styling in a styled() component

As your styling gets more complex, you can extract the styles to a separate file, then create your own theme-aware component using styled().

For better TypeScript support, you can wrap extracted styles in the css() function.

Example

import { css } from '@vitality-ds/system';
// style.css.ts
const style = css({
  color: getColorScaleValueByUseCase(
    "primary",
    text.hiContrast
  ),
  marginBottom: '$md',
  padding: '$lg',
  '&:hover': {
    backgroundColor: getColorScaleValueByUseCase(
      "neutralA",
      backgrounds.uiElement_hovered
    ),
  },
  variants: {
    state: {

      critical: {
        backgroundColor: getColorScaleValueByUseCase(
          "critical",
          backgrounds.uiElement
        ),
      }
      success: {
        backgroundColor: getColorScaleValueByUseCase(
          "success",
          backgrounds.uiElement
        ),
      }
    }
  }
});

// Status.tsx
import style from './style.css';

// Here we create a styled component and pass in our styles
const Status = styled('div', style);

const Example = () => {
  const [isError, setIsError] = React.useState();

  // Now, we set a conditional 'state' variant
  return (
    <Status state={isError ? 'critical' : 'success' }>
      My great component
    </Status>
  );
};

To keep files neat, we recommend extracting styles to a separate file and co-locating creating the styled() components in a styled.ts file. This structure is used by Vitality's core package and allows components to scale as any new styles/components are added.

// components/
// -- MyComponent
// ---- index.tsx
// ---- styled.ts
// ------- styles
// ---------- BaseComponent.styles.ts
// ---------- BaseWrapper.styles.ts // each additional component has separate style files

/**
 * BaseComponent.styles.ts
 */
import { css } from "@vitality-ds/system";
// css function to get Intellisense on values 🔥
export default css({
  marginBottom: "$md",
  color: getColorScaleValueByUseCase("primary", text.hiSaturation),
});

/**
 * styled.ts
 */
import { styled } from "@vitality-ds/system";
import BaseComponentStyles from "./styles/BaseComponent.styles.ts";

export const MyComponent = styled("div", style);

/**
 * index.tsx
 */
import { styled } from "@vitality-ds/system";
import { BaseComponent } from "./styled";

<BaseComponent>My great component</BaseComponent>;

Common CSS selectors

Refer to the Stitches docs on styling for the most up to date documentation.

Pseudo class

() => {
  const Button = styled("button", {
    // base styles
    opacity: 0.4,
    "&:hover": {
      opacity: "1",
    },
  });

  return <Button>Button</Button>;
};

Pseudo-element

() => {
  const MockTooltip = styled("div", {
    position: "relative",

    "&::before": {
      content: "attr(data-text)",
      position: "absolute",
      top: "50%",
      transform: "translateY(-50%)",
      left: "100%",
      marginLeft: "$lg",
      width: 100,
      padding: "$md",
      borderRadius: "$default",
      backgroundColor: "$greyA12",
      color: "$grey1",
      textAlign: "center",
      display: "none",
    },
    "&:hover::before": {
      display: "block",
    },
  });

  return (
    <MockTooltip data-text="Tooltip using pseudo elements">
      Hover Me
    </MockTooltip>
  );
};

Class selector

() => {
  const FancyButton = styled("button", {
    // base styles

    "&.custom-class": {
      boxShadow: "0 0 0 3px coral",
    },
  });

  return (
    <FancyButton variant="outline" className="custom-class">
      Button
    </FancyButton>
  );
};

Attribute Selector

() => {
  const FancyButton = styled("button", {
    // base styles

    "&[data-custom-attribute]": {
      boxShadow: "0 0 0 3px royalblue",
    },
  });

  return <FancyButton data-custom-attribute>Button</FancyButton>;
};

Descendant Selector

() => {
  const FancyButton = styled("button", {
    "& svg": {
      // ...
    },
  });

  return (
    <FancyButton>
      Button <svg>...</svg>
    </FancyButton>
  );
};

Combinator Selector

() => {
  const Button = styled("button", {
    // base styles

    "& + &": {
      marginLeft: "10px",
    },
  });

  return (
    <div>
      <Button>Button</Button>
      <Button>Button</Button>
    </div>
  );
};

Responsive Styles

All component variants can be set based on certain breakpoints. This is particularly useful when altering layouts or visual properties depending on the screen size.

The values of each breakpoint are set in Vitality's config file:

Note that because the breakpoints are using min-width, you set styles based on small/mobile screens and apply breakpoints as the screen size increases.

Syntax

<Typography
  variant={{
    "@initial": "body",
    "@bp1": "pageTitle",
  }}
>
  Responsive Page Title
</Typography>

Example

The below example shows how we switch the properties of a component based on its breakpoint.

<Stack
  spacing={{ "@initial": "lg", "@bp1": "md" }}
  align={{ "@initial": "stretch", "@bp1": "end" }}
>
  <Button>Add Medication</Button>
  <Button appearance="primary">Attach Letter</Button>
  <Button size={{ "@initial": "compact", "@bp1": "default" }}>
    Edit Patient
  </Button>
</Stack>

Adding responsiveness in a component's style definition

For components whose responsive behaviour needs to be "baked in", use the below syntax to add its responsive behaviour.

() => {
  const MyComponent = styled("div", {
    color: "$primary",

    "@bp1": {
      color: "$accent",
    },

    "@bp2": {
      color: "$warning",
      maxWidth: 200,
      backgroundColor: "$text",
      padding: "$lg",
    },
  });

  return (
    <MyComponent>
      Component will change colour depending on the screen size.
    </MyComponent>
  );
};

© 2025