You are looking at the documentation of the work in progress Hope UI 1.0, examples and information may be broken or outdated.

Theming

Style config API

Create custom components with multiple parts, multi-variant styles, and theme customization support.

Warning: This is an advanced API used internally by Hope UI components. In most cases, you don't need it in your application.

Introduction

The style config API allows you to create components with support for:

  • multiple parts (ex: an Alert component with a root, title, and description parts)
  • multi-variant styles (like described in the Styled components section)
  • customization at the theme level (as described in the Customize theme section)
  • polymorphic as prop, style props, and sx

The below example shows how to create a custom Alert component with style config API support.

Creating a style config

Defining component parts

First, we define which parts compose our component.

tsx
// alert.styles.ts
export type AlertParts = "root" | "title" | "description";
tsx
// alert.styles.ts
export type AlertParts = "root" | "title" | "description";

Defining variants

Then, we define which variants our component will accept. Each variant will become a prop of the component.

tsx
// alert.styles.ts
export type AlertParts = "root" | "title" | "description";
interface AlertVariants {
variant: "solid" | "soft";
status: "success" | "info" | "warning" | "danger";
}
tsx
// alert.styles.ts
export type AlertParts = "root" | "title" | "description";
interface AlertVariants {
variant: "solid" | "soft";
status: "success" | "info" | "warning" | "danger";
}

Creating the styles

Next, we create our component's style config.

The createStyleConfig function takes an object as the first parameter, in which each key refers to a part of the component, and each value defines the baseStyle, variants and compoundVariants styles of that part.

tsx
// alert.styles.ts
import { createStyleConfig } from "@hope-ui/core";
export type AlertParts = "root" | "title" | "description";
interface AlertVariants {
variant: "solid" | "soft";
status: "success" | "info" | "warning" | "danger";
}
export const useAlertStyleConfig = createStyleConfig<AlertParts, AlertVariants>({
root: {
baseStyle: {
// base style of the "root" part
},
variants: {
variant: {
solid: {
// style of the "root" part when the `variant="solid"` prop is passed
},
soft: {},
},
status: {
success: {
// style of the "root" part when the `status="success"` prop is passed
},
info: {},
warning: {},
danger: {},
},
},
compoundVariants: [
{
variants: {
variant: "solid",
status: "success",
},
style: {
// style of the "root" part when both `variant="solid"` and `status="success"` props are passed
},
},
],
},
title: {
// style config of the "title" part, if any
},
description: {
// style config of the "description" part, if any
},
});
tsx
// alert.styles.ts
import { createStyleConfig } from "@hope-ui/core";
export type AlertParts = "root" | "title" | "description";
interface AlertVariants {
variant: "solid" | "soft";
status: "success" | "info" | "warning" | "danger";
}
export const useAlertStyleConfig = createStyleConfig<AlertParts, AlertVariants>({
root: {
baseStyle: {
// base style of the "root" part
},
variants: {
variant: {
solid: {
// style of the "root" part when the `variant="solid"` prop is passed
},
soft: {},
},
status: {
success: {
// style of the "root" part when the `status="success"` prop is passed
},
info: {},
warning: {},
danger: {},
},
},
compoundVariants: [
{
variants: {
variant: "solid",
status: "success",
},
style: {
// style of the "root" part when both `variant="solid"` and `status="success"` props are passed
},
},
],
},
title: {
// style config of the "title" part, if any
},
description: {
// style config of the "description" part, if any
},
});

Even if some parts of your component don't require styles, you have to define them with an empty object.

Adding default variants

You can set default variants to be used in the style config by passing a second parameter to createStyleConfig:

tsx
// alert.styles.ts
import { createStyleConfig } from "@hope-ui/core";
export type AlertParts = "root" | "title" | "description";
interface AlertVariants {
variant: "solid" | "soft";
status: "success" | "info" | "warning" | "danger";
}
export const useAlertStyleConfig = createStyleConfig<AlertParts, AlertVariants>(
{
// ...style config definition
},
{
variant: "solid",
status: "info",
}
);
tsx
// alert.styles.ts
import { createStyleConfig } from "@hope-ui/core";
export type AlertParts = "root" | "title" | "description";
interface AlertVariants {
variant: "solid" | "soft";
status: "success" | "info" | "warning" | "danger";
}
export const useAlertStyleConfig = createStyleConfig<AlertParts, AlertVariants>(
{
// ...style config definition
},
{
variant: "solid",
status: "info",
}
);

Creating props type

Using the StyleConfigProps utility type, we can generate a props type from the style config.

tsx
// alert.styles.ts
import { createStyleConfig, StyleConfigProps } from "@hope-ui/core";
export type AlertParts = "root" | "title" | "description";
interface AlertVariants {
variant: "solid" | "soft";
status: "success" | "info" | "warning" | "danger";
}
export const useAlertStyleConfig = createStyleConfig<AlertParts, AlertVariants>({
// ...style config definition
});
export type AlertStyleConfigProps = StyleConfigProps<typeof useAlertStyleConfig>;
tsx
// alert.styles.ts
import { createStyleConfig, StyleConfigProps } from "@hope-ui/core";
export type AlertParts = "root" | "title" | "description";
interface AlertVariants {
variant: "solid" | "soft";
status: "success" | "info" | "warning" | "danger";
}
export const useAlertStyleConfig = createStyleConfig<AlertParts, AlertVariants>({
// ...style config definition
});
export type AlertStyleConfigProps = StyleConfigProps<typeof useAlertStyleConfig>;

This type is made up of the AlertVariants type defined above as Partial and two more props:

  • styleConfigOverride: used to override the base style config created with createStyleConfig
  • unstyled: Whether the base style config created with createStyleConfig should be applied

Integrate with the components

Now, let's create the Alert components that will consume our style config.

Creating the components

We create our components with the createHopeComponent function, which adds support for the polymorphic as prop, style props, and sx.

tsx
// alert.tsx
import { createHopeComponent } from "@hope-ui/core";
import { AlertStyleConfigProps } from "./alert.styles.ts";
export const Alert = createHopeComponent<"div", AlertStyleConfigProps>(props => {
// TODO
});
export const AlertTitle = createHopeComponent<"div">(props => {
// TODO
});
export const AlertDescription = createHopeComponent<"div">(props => {
// TODO
});
tsx
// alert.tsx
import { createHopeComponent } from "@hope-ui/core";
import { AlertStyleConfigProps } from "./alert.styles.ts";
export const Alert = createHopeComponent<"div", AlertStyleConfigProps>(props => {
// TODO
});
export const AlertTitle = createHopeComponent<"div">(props => {
// TODO
});
export const AlertDescription = createHopeComponent<"div">(props => {
// TODO
});

Consuming the style config

Let's start by consuming our style config in the root Alert component.

The useAlertStyleConfig we've created takes two parameters:

  • A name of your choice, used for theme level customization (described below)
  • The styleConfigOverride, unstyled and other variant props

It returns two accessors:

  • baseClasses: the base classes to apply to each part of the component
  • styleOverrides: the style overrides for each part of the component
tsx
// alert.tsx
import { Box, createHopeComponent } from "@hope-ui/core";
import { clsx } from "clsx";
import { splitProps } from "solid-js";
import { AlertStyleConfigProps, useAlertStyleConfig } from "./alert.styles.ts";
export const Alert = createHopeComponent<"div", AlertStyleConfigProps>(props => {
const [local, styleConfigProps, others] = splitProps(
props,
["class", "children"],
["styleConfigOverride", "unstyled", "variant", "status"]
);
const { baseClasses, styleOverrides } = useAlertStyleConfig("Alert", styleConfigProps);
return (
<Box class={clsx(baseClasses().root, local.class)} __css={styleOverrides().root} {...others}>
{local.children}
</Box>
);
});
// ...AlertTitle and AlertDescription components
tsx
// alert.tsx
import { Box, createHopeComponent } from "@hope-ui/core";
import { clsx } from "clsx";
import { splitProps } from "solid-js";
import { AlertStyleConfigProps, useAlertStyleConfig } from "./alert.styles.ts";
export const Alert = createHopeComponent<"div", AlertStyleConfigProps>(props => {
const [local, styleConfigProps, others] = splitProps(
props,
["class", "children"],
["styleConfigOverride", "unstyled", "variant", "status"]
);
const { baseClasses, styleOverrides } = useAlertStyleConfig("Alert", styleConfigProps);
return (
<Box class={clsx(baseClasses().root, local.class)} __css={styleOverrides().root} {...others}>
{local.children}
</Box>
);
});
// ...AlertTitle and AlertDescription components

Note that styleOverrides are passed to the __css prop. It's like the sx prop but has lower specificity. This allows us to override the component styles with style props and sx later.

Providing style config to descendants

We need a way to access the baseClasses and styleOverrides in AlertTitle and AlertDescription components. The easiest way to do that is to use a context.

tsx
// alert.tsx
import { Box, createHopeComponent, SystemStyleObject } from "@hope-ui/core";
import { clsx } from "clsx";
import { Accessor, createContext, useContext, splitProps } from "solid-js";
import { AlertParts, AlertStyleConfigProps, useAlertStyleConfig } from "./alert.styles.ts";
export interface AlertContextValue {
baseClasses: Accessor<Record<AlertParts, string>>;
styleOverrides: Accessor<Record<AlertParts, SystemStyleObject>>;
}
export const AlertContext = createContext<AlertContextValue>();
export function useAlertContext() {
return useContext(AlertContext);
}
export const Alert = createHopeComponent<"div", AlertStyleConfigProps>(props => {
const [local, styleConfigProps, others] = splitProps(
props,
["class", "children"],
["styleConfigOverride", "unstyled", "variant", "status"]
);
const { baseClasses, styleOverrides } = useAlertStyleConfig("Alert", styleConfigProps);
return (
<AlertContext.Provider value={{ baseClasses, styleOverrides }}>
<Box class={clsx(baseClasses().root, local.class)} __css={styleOverrides().root} {...others}>
{local.children}
</Box>
</AlertContext.Provider>
);
});
// ...AlertTitle and AlertDescription components
tsx
// alert.tsx
import { Box, createHopeComponent, SystemStyleObject } from "@hope-ui/core";
import { clsx } from "clsx";
import { Accessor, createContext, useContext, splitProps } from "solid-js";
import { AlertParts, AlertStyleConfigProps, useAlertStyleConfig } from "./alert.styles.ts";
export interface AlertContextValue {
baseClasses: Accessor<Record<AlertParts, string>>;
styleOverrides: Accessor<Record<AlertParts, SystemStyleObject>>;
}
export const AlertContext = createContext<AlertContextValue>();
export function useAlertContext() {
return useContext(AlertContext);
}
export const Alert = createHopeComponent<"div", AlertStyleConfigProps>(props => {
const [local, styleConfigProps, others] = splitProps(
props,
["class", "children"],
["styleConfigOverride", "unstyled", "variant", "status"]
);
const { baseClasses, styleOverrides } = useAlertStyleConfig("Alert", styleConfigProps);
return (
<AlertContext.Provider value={{ baseClasses, styleOverrides }}>
<Box class={clsx(baseClasses().root, local.class)} __css={styleOverrides().root} {...others}>
{local.children}
</Box>
</AlertContext.Provider>
);
});
// ...AlertTitle and AlertDescription components

Then, in AlertTitle and AlertDescription, we use useAlertContext to consume that context.

tsx
// alert.tsx
// ...imports, context and Alert component
export const AlertTitle = createHopeComponent<"div">(props => {
const [local, others] = splitProps(props, ["class"]);
const { baseClasses, styleOverrides } = useAlertContext();
return (
<Box
class={clsx(baseClasses().title, local.class)}
__css={styleOverrides().title}
{...others}
/>
);
});
export const AlertDescription = createHopeComponent<"div">(props => {
const [local, others] = splitProps(props, ["class"]);
const { baseClasses, styleOverrides } = useAlertContext();
return (
<Box
class={clsx(baseClasses().description, local.class)}
__css={styleOverrides().description}
{...others}
/>
);
});
tsx
// alert.tsx
// ...imports, context and Alert component
export const AlertTitle = createHopeComponent<"div">(props => {
const [local, others] = splitProps(props, ["class"]);
const { baseClasses, styleOverrides } = useAlertContext();
return (
<Box
class={clsx(baseClasses().title, local.class)}
__css={styleOverrides().title}
{...others}
/>
);
});
export const AlertDescription = createHopeComponent<"div">(props => {
const [local, others] = splitProps(props, ["class"]);
const { baseClasses, styleOverrides } = useAlertContext();
return (
<Box
class={clsx(baseClasses().description, local.class)}
__css={styleOverrides().description}
{...others}
/>
);
});

Creating a component theme type

We need to create a component theme type to get TypeScript support in the theme object. To do that, Hope UI exposes the ComponentTheme utility type.

In the example below, only variant and status from the AlertStyleConfigProps type will be available to set in the theme default props section.

tsx
// alert.tsx
import { Box, ComponentTheme, createHopeComponent } from "@hope-ui/core";
import { clsx } from "clsx";
import { splitProps } from "solid-js";
import { AlertParts, AlertStyleConfigProps, useAlertStyleConfig } from "./alert.styles.ts";
export type AlertTheme = ComponentTheme<AlertStyleConfigProps, "variant" | "status">;
export const Alert = createHopeComponent<"div", AlertStyleConfigProps>(props => {
const [local, styleConfigProps, others] = splitProps(
props,
["class", "children"],
["styleConfigOverride", "unstyled", "variant", "status"]
);
const { baseClasses, styleOverrides } = useAlertStyleConfig("Alert", styleConfigProps);
return (
<AlertContext.Provider value={{ baseClasses, styleOverrides }}>
<Box class={clsx(baseClasses().root, local.class)} __css={styleOverrides().root} {...others}>
{local.children}
</Box>
</AlertContext.Provider>
);
});
// ...other components
tsx
// alert.tsx
import { Box, ComponentTheme, createHopeComponent } from "@hope-ui/core";
import { clsx } from "clsx";
import { splitProps } from "solid-js";
import { AlertParts, AlertStyleConfigProps, useAlertStyleConfig } from "./alert.styles.ts";
export type AlertTheme = ComponentTheme<AlertStyleConfigProps, "variant" | "status">;
export const Alert = createHopeComponent<"div", AlertStyleConfigProps>(props => {
const [local, styleConfigProps, others] = splitProps(
props,
["class", "children"],
["styleConfigOverride", "unstyled", "variant", "status"]
);
const { baseClasses, styleOverrides } = useAlertStyleConfig("Alert", styleConfigProps);
return (
<AlertContext.Provider value={{ baseClasses, styleOverrides }}>
<Box class={clsx(baseClasses().root, local.class)} __css={styleOverrides().root} {...others}>
{local.children}
</Box>
</AlertContext.Provider>
);
});
// ...other components

Add theme default props support

We also need to allow default props from the theme to be merged in the component.

To do that, we use the mergeThemeProps function and pass it the following parameters:

  1. The name used to retrieve the component in the theme (should be the same used in useAlertStyleConfig)
  2. The fallback default props (if the theme does not provide them)
  3. The component props object
tsx
// alert.tsx
import { Box, ComponentTheme, createHopeComponent, mergeThemeProps } from "@hope-ui/core";
import { clsx } from "clsx";
import { splitProps } from "solid-js";
import { AlertParts, AlertStyleConfigProps, useAlertStyleConfig } from "./alert.styles.ts";
export type AlertTheme = ComponentTheme<AlertStyleConfigProps, "variant" | "status">;
export const Alert = createHopeComponent<"div", AlertStyleConfigProps>(props => {
props = mergeThemeProps("Alert", { status: "info" }, props);
const [local, styleConfigProps, others] = splitProps(
props,
["class", "children"],
["styleConfigOverride", "unstyled", "variant", "status"]
);
const { baseClasses, styleOverrides } = useAlertStyleConfig("Alert", styleConfigProps);
return (
<AlertContext.Provider value={{ baseClasses, styleOverrides }}>
<Box class={clsx(baseClasses().root, local.class)} __css={styleOverrides().root} {...others}>
{local.children}
</Box>
</AlertContext.Provider>
);
});
// ...other components
tsx
// alert.tsx
import { Box, ComponentTheme, createHopeComponent, mergeThemeProps } from "@hope-ui/core";
import { clsx } from "clsx";
import { splitProps } from "solid-js";
import { AlertParts, AlertStyleConfigProps, useAlertStyleConfig } from "./alert.styles.ts";
export type AlertTheme = ComponentTheme<AlertStyleConfigProps, "variant" | "status">;
export const Alert = createHopeComponent<"div", AlertStyleConfigProps>(props => {
props = mergeThemeProps("Alert", { status: "info" }, props);
const [local, styleConfigProps, others] = splitProps(
props,
["class", "children"],
["styleConfigOverride", "unstyled", "variant", "status"]
);
const { baseClasses, styleOverrides } = useAlertStyleConfig("Alert", styleConfigProps);
return (
<AlertContext.Provider value={{ baseClasses, styleOverrides }}>
<Box class={clsx(baseClasses().root, local.class)} __css={styleOverrides().root} {...others}>
{local.children}
</Box>
</AlertContext.Provider>
);
});
// ...other components

The name provided to mergeThemeProps should be the same used in useAlertStyleConfig.

Using the component

Finally, our Alert component can be used like this:

tsx
<Alert variant="solid" status="info">
<AlertTitle>Get ready!</AlertTitle>
<AlertDescription>Hope UI 1.0 is going live soon.</AlertDescription>
</Alert>
tsx
<Alert variant="solid" status="info">
<AlertTitle>Get ready!</AlertTitle>
<AlertDescription>Hope UI 1.0 is going live soon.</AlertDescription>
</Alert>

It also supports the polymorphic as prop, style props, and sx.

tsx
<Alert variant="solid" status="info" as="section" p={4}>
<AlertTitle as="h2" fontWeight="semibold" mb={2}>
Get ready!
</AlertTitle>
<AlertDescription as="span">Hope UI 1.0 is going live soon.</AlertDescription>
</Alert>
tsx
<Alert variant="solid" status="info" as="section" p={4}>
<AlertTitle as="h2" fontWeight="semibold" mb={2}>
Get ready!
</AlertTitle>
<AlertDescription as="span">Hope UI 1.0 is going live soon.</AlertDescription>
</Alert>
Previous
Customize theme