Giter VIP home page Giter VIP logo

Comments (27)

nmn avatar nmn commented on August 30, 2024 9

There's a lot here already, so I'll try to just answer in the initial question. Using dark mode based on a user controlled data-id. This can be done by creating default variables with defineVars and then creating separate themes with createTheme.

Something like:

// vars.stylex.js
import * as stylex from '@stylexjs/stylex';

export const colors = stylex.defineVars({
  primary: 'red',
  // ...
});
// color-themes.js
import * as stylex from '@stylexjs/stylex';
import {colors} from './vars.stylex.js';

export const darkTheme = stylex.createTheme(colors, {
  primary: 'blue',
  // ...
});
// layout.tsx

import {darkTheme} from './color-themes';

export default function RootLayout({isDarkMode, children}) {
  return (
    <html {...stylex.props(isDarkMode && darkTheme)}>
      <head>{/* ... */}</head>
      <body>
        {children}
      </body>
    </html>
  );
}

Now you can just import and use the variables from vars.stylex.ts directly
and use them. They will resolve to light mode or not based on whether
the theme is set on the html tag or not.


Things to note:

  • You don't need the data-id for StyleX, but you can keep it if you want it for other things.
  • The layout.tsx is an example for how this might be done in NextJS. You can set the theme
    on the html element any way you want. e.g. If you're using DOM APIs for this, that will work too.
  • Remember that you must always import the variables from the original defineVars export.

If you want a "light" theme, a "dark" theme, and a default theme that uses @media to change automatically you can do that too. Change the original variables defined with defineVars to use media queries, then create two themes for light and dark that don't use media queries.

from stylex.

olivierpascal avatar olivierpascal commented on August 30, 2024 2

Or, to generate a style like:

:root {
  --primary: red;
  --primary-dark: blue;
}

.myComponent {
  color: var(--primary);
}

:is([data-mode="dark"]) .myComponent {
  color: var(--primary-dark);
}

Maybe from something like:

// tokens.stylex.js

import * as stylex from '@stylexjs/stylex';

export const colors = stylex.defineVars({
  primary: 'red',
  'primary-dark': 'blue',
});
// MyComponent.jsx

import { colors } from './tokens.stylex';

const styles = stylex.create({
  myComponent: {
    color: {
      default: colors.primary,
      ':is[dark-mode="dark"]': colors['primary-dark'],
    }
  },
});

// ...

from stylex.

nmn avatar nmn commented on August 30, 2024 2

I didn't cover the JS bits but @kevintyj's suggestion is valid for React.

This way you would not have to create one defineVars and two separate themes to use it with @media

I'm not clear what this means so I'll try to clarify what I meant by multiple themes.

Case 1: Light Theme is default. Dark theme can be enabled manually

Use defineVars to create the default light theme, use createTheme to create a dark theme. Apply dark theme conditionally as suggested by @kevintyj

Case 2: System theme is default. Light and dark themes can be enabled manually

Use defineVars and use the media query to make this theme adapt to the media query. Use createTheme to create the "light" and "dark" themes that always use the light or dark colors respectively.

from stylex.

purohitdheeraj avatar purohitdheeraj commented on August 30, 2024 1

hey @olivierpascal
how about using the required component as nested object
psuedoSelector(dark).myComponent : colorValue
psuedoSelector(light).myComponent : colorValue

for this there is a reference on docs, see if it helps
defining styles

correct me if it doesn't relate to your usecase

from stylex.

olivierpascal avatar olivierpascal commented on August 30, 2024 1

@nmn your solution works like a charm, thank you. I have nothing to add, maybe we can close this topic? Thanks all very much.

from stylex.

olivierpascal avatar olivierpascal commented on August 30, 2024 1

I had this bug the very first time I used stylex (with some code straight from the doc) but, as I stopped using media queries, I forgot to create an issue.

On my side, the following does work:

export const colorsRepo = {
  background: 'red',
};
export const colorsRepo = {
  background: {
    default: 'red', // work
    whatever: 'lightgray',
  },
};

When the following silently fail (the entire var group is not set):

export const colorsRepo = {
  background: {
    default: 'red',
    '@media (prefers-color-scheme: dark)': 'lightgray',
  },
};

If you can reproduce this bug, maybe create a new issue with a minimal reproducible code?

from stylex.

nmn avatar nmn commented on August 30, 2024 1

I'm using Astro and during server-side rendering, dark mode is unknown so it defaults to light mode.

This is the problem here. We have media queries for detecting dark mode. You shouldn't be trying to choose light mode or dark mode on the server at all.

I'm working on adding first-class support for light-dark(). Which will make doing this even more flexible.

But TLDR; Setting themes based on selectors such as html[data-theme='dark'] should be avoided as a best practice. But even if you're using them, they should only be used when the user has manually overridden their preferred color scheme which should be known on the server. If you don't know their preferred color scheme, you should be using Media Queries to choose automatically. (And use light-dark() in the future)

from stylex.

nmn avatar nmn commented on August 30, 2024 1

FWIW, the stylex website uses html[data-theme=dark].

The only SSR-safe ways of doing color theming is:

  1. To store the user pref
  2. Use media queries

Docusaurus generates a static bundle so there is no server to store user preferences on.

:(

from stylex.

timwehrle avatar timwehrle commented on August 30, 2024

You can have a look at the Next.js example to see how it might be done. But I don't think it works like you want to make it work in styleX.

I don't know if this is your question or not, but try to be explicit about what you want support for. Or describe the solution you're looking for.

from stylex.

olivierpascal avatar olivierpascal commented on August 30, 2024

Of course: for the needs of my app, i need to be able to demo a component in both light and dark mode on the same screen.

<div data-mode="light">
  <MyStyledComponent className="myComponent" />
</div>

<div data-mode="dark">
  <MyStyledComponent className="myComponent" />
</div>

I cannot use @media (prefers-color-scheme: light/dark) because this is OS specific (unaffected by the color-scheme css property).

I would like to be able to generate a style like:

:root {
  --primary: red;
}

:is([data-mode="dark"]) {
  --primary: blue;
}

.myComponent {
  color: var(--primary);
}

Maybe from something like:

// tokens.stylex.js

import * as stylex from '@stylexjs/stylex';

export const colors = stylex.defineVars({
  primary: {
    default: 'red',
    ':is[dark-mode="dark"]': 'blue',
  },
});
// MyComponent.jsx

import { colors } from './tokens.stylex';

const styles = stylex.create({
  myComponent: {
    color: colors.primary,
  },
});

// ...

What do you think?

from stylex.

olivierpascal avatar olivierpascal commented on August 30, 2024

I know this syntax would conflict with pseudo classes, but you get the idea.

from stylex.

olivierpascal avatar olivierpascal commented on August 30, 2024

Mmmh, the problem is that the pseudo selector :is([dark-mode="dark"]) should be at the root level and hence placed before the class name, like :is([data-mode="dark"]) .myComponent {} or :is([data-mode="dark"] .myComponent) {}.

I don't think it's possible to do so with a nested object, at the component level.

from stylex.

olivierpascal avatar olivierpascal commented on August 30, 2024

I think for my use case, the best API would be something like:

// tokens.stylex.js

import * as stylex from '@stylexjs/stylex';

export const colors = stylex.defineVars({
  primary: {
    default: 'red',
    ':is[dark-mode="dark"]': 'blue',
  },
  secondary: {
    default: 'green',
    ':is[dark-mode="dark"]': 'black',
  },
});

-or-

// tokens.stylex.js

import * as stylex from '@stylexjs/stylex';

export const colors = stylex.defineVars({
  default: {
    primary: 'red',
    secondary: 'green',
  },
  ':is[dark-mode="dark"]': {
    primary: 'blue',
    secondary: 'dark',
  },
});

to generate:

/* stylex.css */

:root {
  --primary: red;
  --secondary: green;
}

:is([data-mode="dark"]) {
  --primary: blue;
  --secondary: black;
}

The remaining question would still be how to do the same at the component level, without the theming API.

from stylex.

olivierpascal avatar olivierpascal commented on August 30, 2024

Sorry to spam. I just see that https://stylexjs.com stylesheet is compiling to this:

:root {
    --ifm-color-primary: #872291;
    /* ... */
}

html[data-theme=dark]:root {
    --ifm-color-primary: #be51c8;
    /* ... */
}

I don't know if it uses stylex or pure css. Probably a mix of both.

from stylex.

kevintyj avatar kevintyj commented on August 30, 2024

Just to add on to the conversation, you can also use a Context (if you are using React or Nextjs) to switch between themes globally similar to most other styling libraries out there

const ThemeContext = React.createContext(undefined)
...
const [theme, setTheme] = useState('light');
...
// You can also append props to children directly through cloneElement (but is discouraged by React team)
<ThemeContext.Provider value={ theme }>
	<body {...props(theme === 'dark' && darkTheme)}>
		{children}
	</body>
</ThemeContext.Provider>

You can also use custom hooks and an effect to automatically set default theme and persist data to local storage.
React docs: https://react.dev/reference/react/createContext shows the same exact application method.
This way you would not have to create one defineVars and two separate themes to use it with @media, you can just create the same defineVars with an added theme as mentioned above by @nmn, but this may not be a recommended use pattern (and why he did not cover it).

from stylex.

doiya46 avatar doiya46 commented on August 30, 2024

@nmn If we possess numerous properties but wish to override certain values, what would be the most effective method to accomplish this? I used ...colors.__tokens but it didn't work, so I have to export base values.

Here is the code I've written, but I think it's not quite right.

// tokens.stylex.tsx
import * as stylex from '@stylexjs/stylex';
const DARK = '@media (prefers-color-scheme: dark)';

export const colorsRepo = {
  primaryText: { default: 'black', [DARK]: 'white' },
  secondaryText: { default: '#333', [DARK]: '#ccc' },
  accent: { default: 'blue', [DARK]: 'lightblue' },
  background: { default: 'white', [DARK]: 'black' },
  lineColor: { default: 'gray', [DARK]: 'lightgray' },
  borderRadius: '4px',
  fontFamily: 'system-ui, sans-serif',
  fontSize: '16px',
};

export const colors = stylex.defineVars(colorsRepo);

export const spacing = stylex.defineVars({
  none: '0px',
  xsmall: '4px',
  small: '8px',
  medium: '12px',
  large: '20px',
  xlarge: '32px',
  xxlarge: '48px',
  xxxlarge: '96px',
});
//themes.ts
import * as stylex from '@stylexjs/stylex';
import { colors, colorsRepo } from './tokens.stylex';

// A constant can be used to avoid repeating the media query
const DARK = '@media (prefers-color-scheme: dark)';

// Dracula theme
export const dracula = stylex.createTheme(colors, {
  ...colorsRepo,
  primaryText: { default: 'purple', [DARK]: 'lightpurple' },
  background: { default: '#555', [DARK]: 'black' },
});
// MyComponent.tsx
'use client';

import * as stylex from '@stylexjs/stylex';
import { colors, spacing } from './tokens.stylex';
import { dracula } from './themes';
import { useState } from 'react';

const styles = stylex.create({
  container: {
    color: colors.primaryText,
    backgroundColor: colors.background,
    padding: spacing.medium,
    marginTop: '10px',
    fontSize: colors.fontSize,
  },
});

export const MyComponent = ({ children }: any) => {
  const [isDark, setDark] = useState(false);
  const toggle = () => {
    setDark(!isDark);
  };
  return (
    <div {...stylex.props(isDark && dracula)}>
      <div {...stylex.props(styles.container)}>
        <button onClick={toggle}>Toggle</button>
        &nbsp;{children}
      </div>
    </div>
  );
};

And one more thing, the class for debug mode is UnknownFile__dracula

I test with https://github.com/facebook/stylex/tree/main/apps/nextjs-example)

<div class="x1wm1iiy UnknownFile__dracula">
  <button>Toggle</button>
    <div class="MyComponent__styles.container x13ca1vq x1lfge5 x1nq940n xf0e2mi">Hello
  </div>
</div>
Screenshot 2023-12-21 at 21 58 16

from stylex.

nmn avatar nmn commented on August 30, 2024

If we possess numerous properties but wish to override certain values, what would be the most effective method to accomplish this?

This was a type constraint that I fixed yesterday. In the next version, you'll be able to override only some of the variables when creating a theme.

@olivierpascal Could you give me a slightly more detailed example?

from stylex.

olivierpascal avatar olivierpascal commented on August 30, 2024

Ok @nmn, here is a full reproducible example:

// tokens.stylex.ts

import * as stylex from '@stylexjs/stylex';

// A constant can be used to avoid repeating the media query
const DARK = '@media (prefers-color-scheme: dark)';

export const colors = stylex.defineVars({
  primary: { default: 'blue', [DARK]: 'green' }, // NOT OK: text output is black
  // primary: 'blue', // OK: text output is blue
  // primary: { default: 'blue', whatever: 'green' }, // OK: text output is blue
});
// MyComponent.tsx

import * as stylex from '@stylexjs/stylex';
import React from 'react';

import { colors } from './tokens.stylex';

const styles = stylex.create({
  container: {
    color: colors.primary,
  },
});

export const MyComponent: React.FC = () => (
  <div {...stylex.props(styles.container)}>TEXT</div>
);

The CSS var is not compiled.

image

from stylex.

olivierpascal avatar olivierpascal commented on August 30, 2024

Probably related to #235

from stylex.

nmn avatar nmn commented on August 30, 2024

@olivierpascal Yup, I think this is caused by #235 as well. Can you try a production build to verify in your case as well?

from stylex.

nmn avatar nmn commented on August 30, 2024

v0.4.1 Fixes the issues mentioned.

from stylex.

alejandroyunes avatar alejandroyunes commented on August 30, 2024

for me this is working both with context and prefers-color-scheme.

//themes.ts
const DARK = '@media (prefers-color-scheme: dark)';

export const lightTheme = stylex.createTheme(colors, {
  primary: 'blue',
  bg: {
    default: "white",
    [DARK]: "white",
  }
})

export const darkTheme = stylex.createTheme(colors, {
  primary: 'blue',
  bg: {
    default: "black",
    [DARK]: "black",
  }
})

how can i make it work without the colors object, If possible. I couldn't get the red or blue to appear both changing the prefers-color-scheme though the browser and by changing the theme via context.

export const colors = stylex.defineVars({
  primary: 'red',
  bg: {
    default: "red",
    [DARK]: "blue",
  }
});

then i made this provider like suggested above:

//providers.tsx
type Theme = 'light' | 'dark'

interface ThemeContextType {
  theme: Theme
  setTheme: Dispatch<SetStateAction<Theme>>
}
export const ThemeContext = createContext<ThemeContextType | undefined>(undefined)

export const ThemeProvider = ({ children }: Props) => {

  const [theme, setTheme] = useState<Theme>('dark')
  const contextValue: ThemeContextType = { theme, setTheme }

  return (
    <ThemeContext.Provider value={contextValue}>
      <html {...stylex.props(s.html, s.reset)} lang="en">
        <body {...stylex.props(s.reset, s.body, theme === 'dark' ? darkTheme : lightTheme)}>
          {children}
        </body>
      </html>
    </ThemeContext.Provider>
  )
}

export const useTheme = () => {
  const context = useContext(ThemeContext)
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider')
  }
  return context
}
//component.ts
  const { setTheme } = useTheme();
//layout.ts
export default async function RootLayout({ children }: { children: React.ReactNode }) {

  return (
    <ThemeProvider>
        {children}
    </ThemeProvider>
  )
}

from stylex.

nmn avatar nmn commented on August 30, 2024

how can i make it work without the colors object, If possible

@alejandroyunes I don't understand what you're asking.

from stylex.

alejandroyunes avatar alejandroyunes commented on August 30, 2024

hi,
yes I think what I was asking is that if primary isnt any of the createThemplate it takes what is in the colors object in defineVars,

//themes.ts
const DARK = '@media (prefers-color-scheme: dark)';

export const lightTheme = stylex.createTheme(colors, {
  bg: {
    default: "white",
    [DARK]: "white",
  }
})

export const darkTheme = stylex.createTheme(colors, {
  bg: {
    default: "black",
    [DARK]: "black",
  }
})
export const colors = stylex.defineVars({
  primary: 'red',
  bg: {
    default: "red",
    [DARK]: "blue",
  }
});

why do I need the bg in colors and in both themes?

from stylex.

nmn avatar nmn commented on August 30, 2024

I still don't understand what you're asking, but you should be able to write this instead:

//themes.ts
const DARK = '@media (prefers-color-scheme: dark)';

export const lightTheme = stylex.createTheme(colors, {
  bg: "white",
})

export const darkTheme = stylex.createTheme(colors, {
  bg: "black",
})
export const colors = stylex.defineVars({
  primary: 'red',
  bg: {
    default: "red",
    [DARK]: "blue",
  }
});

why do I need the bg in colors and in both themes?

You don't...

from stylex.

cly avatar cly commented on August 30, 2024

Sorry to bring up an old thread but I'm not able to do dark mode without FOUCs.

Given this example...

// layout.tsx

import {darkTheme} from './color-themes';

export default function RootLayout({isDarkMode, children}) {
  return (
    <html {...stylex.props(isDarkMode && darkTheme)}>
      <head>{/* ... */}</head>
      <body>
        {children}
      </body>
    </html>
  );
}

I'm using Astro and during server-side rendering, dark mode is unknown so it defaults to light mode. Now on client hydration we get the light mode rendered, then once the React code kicks in and recognizes that dark mode is set, it re-renders.

The way I got this working in other styling libraries is by using descendent selectors similar to html[data-theme='dark'] .test. Even on the first render from SSR, the styles are correct. Is there an escape hatch here?

Thanks so much @nmn!

from stylex.

cly avatar cly commented on August 30, 2024

Gotcha, so the rec is to store user prefs for dark / light on server and then use that for server side rendering.

FWIW, the stylex website uses html[data-theme=dark].

from stylex.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.