v14.2.0

Playroom

Github

Theming

View Source

UI kit ships with sensible defaults for all themeable properties that can be fully overridden.

ThemeProvider

The ThemeProvider component is responsible for overriding themeable values throughout a tree of UI kit components.

Props

theme: Theme

Provide a custom theme object with the respective themeable properties you would like to change. Note that your theme will be merged with the default theme, so you only need to override or add themeable values that matter to your use case.

Example

import { StackView, Text, ThemeProvider } from '@planning-center/ui-kit'
const theme = {
boxSizes: {
small: { paddingHorizontal: 1, paddingHorizontal: 0.75 },
medium: { paddingHorizontal: 1.5, paddingHorizontal: 1 },
},
breakpoints: {
phone: 480,
tablet: 720,
desktop: 960,
},
colors: {
primary: 'hotpink',
secondary: 'lime',
},
}
function App() {
return (
<ThemeProvider theme={theme}>
<StackView axis="horizontal" spacing={0.5}>
<Text color="primary">Hello</Text>
<Text color="secondary">World</Text>
</StackView>
</ThemeProvider>
)
}
render(App)

Themeable Properties

boxSizes

Box sizes are used for Avatar, Badge, Button, Input, and StepperField components.

const theme = {
boxSizes: {
sm: {
boxSize: 3,
fontSize: 5,
lineHeight: 2.5,
paddingHorizontalDense: 0.5,
paddingHorizontal: 1,
paddingVertical: 0.25,
radius: 3,
},
md: {
boxSize: 4,
fontSize: 4,
lineHeight: 3,
paddingHorizontalDense: 1,
paddingHorizontal: 1.375,
paddingVertical: 0.5,
radius: 4,
},
},
}

breakpoints

UI kit uses Planning Center breakpoints by default. See how breakpoints are defined and customized in the responsive guide.

button

themes

Button themes must provide all 3 variants fill, outline, and naked for each theme. See what themes are provided by default.

A simple button theme that overrides the primary theme might look like the following:

const theme = {
button: {
themes: {
primary: {
fill: {
backgroundColor: 'purple-5',
color: 'white',
},
outline: {
stroke: 'purple-3',
color: 'purple-9',
},
naked: {
color: 'purple-3',
},
},
},
},
}

colors

Colors can be fully customized or integrated with your existing system through CSS custom properties. See how colors can be defined in the colors guide.

checkbox

Allows modifying the following properties for Checkbox:

const theme = {
checkbox: {
fill: 'surfaceTertiary',
stroke: 'separatorSecondary',
focusStroke: 'blue-5',
checkedFill: 'primary-light',
checkedStroke: 'primary',
},
}

icons

Icons can be fully customized or integrated with your existing system. The only requirement is that you only export path data. See Icon for an example of using custom sets of icons with ThemeProvider.

import * as calendar from '@planningcenter/icons/paths/calendar'
import * as general from '@planningcenter/icons/paths/general'
const theme = {
icons: {
calendar,
general,
},
icon: {
viewBox: '0 0 16 16',
},
}

page

Theme keys for most Page components.

const theme = {
pageBody: { backgroundColor: 'backgroundSecondary' },
pageButton: { theme: 'white' },
pageDropdown: { theme: 'white', variant: 'outline' },
pageHeader: { backgroundColor: 'primary-light' },
pageTitle: { level: 2, color: 'white' },
}

radio

Allows modifying the following properties for Radio:

const theme = {
radio: {
fill: 'surfaceTertiary',
stroke: 'separatorSecondary',
focusStroke: 'blue-5',
checkedFill: 'primary-light',
checkedStroke: 'primary',
},
}

spinner

Spinner sizes and thicknesses can be customized by assigning an object with new values to the size keys of xxs-xxl:

const theme = {
spinner: {
sizes: { xxs: 1, xs: 2, sm: 3, md: 4, lg: 5, xl: 6, xxl: 7 },
thickness: { xxs: 2, xs: 2, sm: 2, md: 3, lg: 4, xl: 6, xxl: 8 },
},
}

Prop Autocompletion

In order to apply your theme to the type system we must augment or override the default system that ships with UI kit. If you don’t have a definitions file yet, create a global.d.ts file at the root of your project. Now we’ll declare a module type to override UI kit, add your specific types that correlate to your ThemeProvider values here:

Note that Breakpoints must be defined in your project in order to get autocompletion for the mediaQueries prop.

import '@planning-center/ui-kit'
declare module '@planning-center/ui-kit' {
export interface Breakpoints {
mobile: number
tablet: number
desktop: number
}
export interface Colors {
'purple-0': string
'purple-1': string
'purple-2': string
'purple-3': string
'purple-4': string
'purple-5': string
'purple-6': string
'purple-7': string
'purple-8': string
'purple-9': string
}
}

You should now have rich autocompletion using your theme settings. Note that in VS Code specifically, if you don’t see the type information update you can restart the TypeScript server by opening the command palette and selecting TypeScript: Restart TS server.

Theming in React Rails

In a React Rails environment there can be multiple roots that React is rendered into. This poses an issue when needing to provide a common component like ThemeProvider to every component. We’ll look at two different ways we can overwrite the react-rails render method.

First, we’ll create an AppProvider component that makes use of UI kit’s ThemeProvider:

import React from 'react'
import { ThemeProvider } from '@planning-center/ui-kit'
function AppProvider({ children }) {
const organizationStore = Services.Flux.getStore('OrganizationCurrentStore')
const theme = {
calendar: {
weekStartsOn: organizationStore
? organizationStore.weekStartsOnSunday()
? 0
: 1
: 0,
},
}
return <ThemeProvider theme={theme}>{children}</ThemeProvider>
}
export default AppProvider

JavaScript

Next, if we want to overwrite the render method in JavaScript we can do this in our application.js file or wherever our JavaScript is initiated.

Add the following snippet to override all of our root components and wrap them with our AppProvider:

import AppProvider from './AppProvider'
// modified from https://github.com/reactjs/react-rails/blob/master/react_ujs/index.js#L83-L121
window.ReactRailsUJS.mountComponents = (searchSelector) => {
let ujs = window.ReactRailsUJS
let nodes = ujs.findDOMNodes(searchSelector)
for (let i = 0; i < nodes.length; ++i) {
let node = nodes[i]
let className = node.getAttribute(ujs.CLASS_NAME_ATTR)
let constructor = ujs.getConstructor(className)
let propsJson = node.getAttribute(ujs.PROPS_ATTR)
let props = propsJson && JSON.parse(propsJson)
let hydrate = node.getAttribute(ujs.RENDER_ATTR)
let cacheId = node.getAttribute(ujs.CACHE_ID_ATTR)
let turbolinksPermanent = node.hasAttribute(ujs.TURBOLINKS_PERMANENT_ATTR)
let shouldTransformProps = node.getAttribute('transform_props') === 'true'
if (!constructor) {
let message = "Cannot find component: '" + className + "'"
if (console && console.log) {
console.log(
'%c[react-rails] %c' + message + ' for element',
'font-weight: bold',
'',
node
)
}
throw new Error(
message + '. Make sure your component is available to render.'
)
} else {
let component = components[cacheId]
if (component === undefined) {
component = React.createElement(
constructor,
shouldTransformProps ? transformProps(props) : props
)
if (turbolinksPermanent) {
components[cacheId] = component
}
}
if (hydrate && typeof ReactDOM.hydrate === 'function') {
ReactDOM.hydrate(<AppProvider>{component}</AppProvider>, node)
} else {
ReactDOM.render(<AppProvider>{component}</AppProvider>, node)
}
}
}
}

Rails

Or if we want to overwrite the view helper we can create our own that utilizes our AppProvider component:

in lib/react_mounter.rb

class ReactMounter < React::Rails::ComponentMount
def react_component(name, props = {}, options = {}, &block)
props[:component] = name
html_tag = super
html_tag.sub!(name, 'AppProvider')
html_tag.html_safe
end
end

in config/application.rb

# ...
require_relative '../lib/react_mounter'
# ...
module People
class Application < Rails::Application
# ...
config.react.view_helper_implementation = ::ReactMounter
# ...
end

Please note that require.context needs to be set up properly in order to resolve the component correctly.

  • ThemeProvider
  • Props
  • theme: Theme
  • Example
  • Themeable Properties
  • boxSizes
  • breakpoints
  • button
  • themes
  • colors
  • checkbox
  • icons
  • page
  • radio
  • spinner
  • Prop Autocompletion
  • Theming in React Rails
  • JavaScript
  • Rails
PreviousResponsiveNextVariants