system-ui / theme-ui

Build consistent, themeable React apps based on constraint-based design principles

Home Page:https://theme-ui.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Be able to override color mode on per-component basis

herrethan opened this issue · comments

Is your feature request related to a problem? Please describe.
This is just an idea for an enhancement, as I've recently come across the need to be able to render dark-mode variants of components in a light mode context. For example, the buttons on the github nav bar could be considered secondary, but in a dark context, although the whole page may be in light mode.

Describe the solution you'd like
I'm thinking of being able to force override the mode on individual components like:

<Button variant="secondary" mode="dark" />

And in fact am building this in the library I'm currently working on.

Working this out is a little bit clumsy because of the need to re-define all the button styles over again, on top of all the theme.colors and theme.buttons variants, all the while being careful to avoid colors affected by global mode changes.

Describe alternatives you've considered
Creating override providers?

<ThemeUIColorMode mode="dark">
  <Button /> // dark button
  <ThemeUIColorMode mode="light">
    <Button /> // light button
  <ThemeUIColorMode/>
<ThemeUIColorMode/>

Though the way the styles provider currently works, this could end up being pretty inefficient.

I could imagine the mode prop on the component working, because then internally we could just update the reference to the component variant colors with the associated theme.colors.modes colors directly, without worrying about touching the gobal mode context. But I suppose that would mean spitting out the new colors into sx or something, to avoid needing a new div wrapping element to declare the new css vars in. 😬

Ended up going with a class-based approach in the theme, like this (simplified)

buttons: {
  primary: {
    color: 'background',
    bg: 'primary',
    '&:hover': {
      bg: 'primaryHover',
    },
    // re-define explicit colors here
    '&.light-mode-override': {
      color: 'white',
      bg: 'black',
      '&:hover': {
        bg: 'grey'
      },
    },
    ...

Which is quite tedious if you have a lot of variants but is the cleanest solution as far as I can tell.

Yeaah, I think I agree. Creating new contexts for this might be performance intensive, so it's fine if you're doing it once on a page level, but it might be not what we want if we just want to override some buttons.

You could probably expand your solution to something like

const createButtonPrimary = (colorMode) => {
  return {
    color: colorMode.primary,
    bg: colorMode.background,
    ':hover': { bg: colorMode.primaryHover }
  }
}

const theme = {
  // ...
  buttons: {
    primary: {
      ...createButtonPrimary(new Proxy({}, { get(_, key) { return key } }),
      '.light-mode-override': createButtonPrimary(colors.light),
      '.dark-mode-override': createButtonPrimary(colors.dark)
    }
  }
}

if you end up needing a lot of those overrides.

@hasparus thanks for the idea! A very sneaky Proxy interceptor pattern I may just steal.