How to properly abstract/alias custom theme settings?

The theme I’m working on can be found here.

Inspired by the great work of @Segun on their Chakra UI Frontity Theme, I decided that I wanted to better abstract aspects of my theme to variables set in it’s settings.

So in src/index.js there is the theme JS object:

import Theme from "./components";
import image from "@frontity/html2react/processors/image";

const desertJackalope = {
  name: "desert-jackalope",
  roots: {
    // In Frontity, any package can add React components to the site.
    // We use roots for that, scoped to the "theme" namespace.
    theme: Theme
  },
  state: {
    // State is where the packages store their default settings and other
    // relevant state. It is scoped to the "theme" namespace.
    theme: {
      isBlog: false,
      colors: {
        primary: {
          default: "#2657eb",
          heavy: "#1f38c5"
        }
      }
    }
  },
  // Actions are functions that modify the state or deal with other parts of
  // Frontity like libraries.
  actions: {
    theme: {}
  },
  libraries: {
    html2react: {
      // Add a processor to html2react so it processes the <img> tags
      // inside the content HTML. You can add your own processors too.
      processors: [image]
    }
  }
};

export default desertJackalope;

As you can see I have added a couple of key-value pairs to the theme object. isBlog determines whether or not the website is set to “blog” mode or if it’s set to “case study mode”. I also set some default theme colors.

isBlog works fine, and as intended which you can see an example of it’s use in src/components/archive/archive.js:

import React from "react";
import { connect } from "frontity";
import BlogArchive from "./BlogArchive";
import CaseStudyArchive from "./CaseStudyArchive";

const Archive = ({ state, data }) => {
  // check whether or not blog or case study presentation

  return (
    <>
      {state.theme.isBlog ? (
        <BlogArchive data={data} />
      ) : (
        <CaseStudyArchive data={data} />
      )}
    </>
  );
};

export default connect(Archive);

However when I try to add the colors to my global css in src/components/index.js, I get a result of undefined. This is how the code currently looks. You can see my commented out attempts at directly importing desertJackalope (the name of the JS object in my src/index.js file) and my attempt at setting the value of desertJackalope.theme.colors.primary.default to the const primaryColor.

import React from "react";
import { Global, css, connect, styled } from "frontity";
import Header from "./Header";
import Archive from "./Archive";
import Post from "./Post";
import Page404 from "./Page404";
import Page from "./Page";
import Loading from "./Loading";
import { useTransition, animated } from "react-spring";
import Meta from "./Meta";
import { colors } from "../theme";
import Footer from "./Footer";
//import desertJackalope from "../index";
//const primaryColor = desertJackalope.theme.colors.primary.default;

const Theme = ({ state }) => {
  const transitions = useTransition(state.router.link, link => link, {
    from: { opacity: 0 },
    enter: { opacity: 1 },
    leave: { opacity: 0 }
  });

  return (
    <>
      <Meta />
      <Global styles={globalStyles} />
      <Header />

      {transitions.map(({ item, props, key }) => {
        const data = state.source.get(item);
        return (
          <animated.div key={key} style={props}>
            <Absolute>
              {(data.isFetching && <Loading />) ||
                (data.isArchive && <Archive data={data} />) ||
                (data.isPage && <Page data={data} />) ||
                (data.isPostType && <Post data={data} />) ||
                (data.is404 && <Page404 />)}
            </Absolute>
          </animated.div>
        );
      })}
      {/* <Footer /> */}
    </>
  );
};

export default connect(Theme);

//- GLOBAL STYLES CSS

//- Color vars
const primaryColor = colors.primary.default;
const heavyprimaryColor = colors.primary.heavy;
const accentColor = colors.accent;
const darkColor = colors.dark[100];
const darkColor90 = colors.dark[90];
const darkColor80 = colors.dark[80];
const darkColor30 = colors.dark[30];

// set global styles
const globalStyles = css`
  @import url("https://fonts.googleapis.com/css?family=Space+Mono:400,400i,700,700i&display=swap");
  :root {
    --primary-heavy: ${heavyprimaryColor};
    --primary: ${primaryColor};
    --snappy: cubic-bezier(0.075, 0.82, 0.165, 1);
    --heavy-snap: cubic-bezier(0.6, -0.28, 0.735, 0.045);
    --accent: ${accentColor};
    --dark: ${darkColor};
    --dark90: ${darkColor90};
    --dark80: ${darkColor80};
    --dark30: ${darkColor30};
    *::selection {
      background: var(--primary);
      color: white;
    }
  }
  body {
    margin: 0;
    font-family: "Space Mono", "Segoe UI", Roboto, "Droid Sans",
      "Helvetica Neue", Helvetica, Arial, sans-serif;
    box-sizing: border-box;
  }
  a,
  a:visited {
    color: inherit;
    text-decoration: none;
  }
`;

//- Page Transition stuff

const Absolute = styled.div`
  position: absolute;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
`;

Directly referencing the desertJackalope.state.theme.colors.primary.default value does not work. It does work when inserted into a component, because all components created inherit state, but trying to set desertJackalope.state.theme.colors.primary.default to const primaryColor and then setting the css variable in global state root to ${primaryColor} results in a react error saying that “primaryColor is undefined”.

If instead I create a JS object in src/theme.js called “colors” and I set it such that colors.primary.default is my default color, and then import the colors object to src/index.js and set const primaryColor to colors.primary.default, I can then set the css global root variable to ${primaryColor} then it works no problem… and I’m not clear why.

Setting all my theme color settings in src/theme.js isn’t too bad but it does mean that I can’t have the option of setting default color settings in my src/index.js file with the option of being overridden by the frontity.settings.js file (as I can do with the isBlog value very easily).

Segun sets his colors in his src/index.js file but he passes all colors to his components via prop drilling. So it works because those components inherit state. But for some reason when I try passing the contents of desertJackalope.state.theme etc to a const and pass that into CSS variables it does not work.

I find prop drilling messy, and I would prefer to abstract/alias my theme colors via my custom src/theme.js file than have to muck up my component markup with a bunch of color variables.

I assume this problem is caused by how frontity is handling the src/index.js object. Clearly the object gets some kind of interaction as the default state set in that file can be overridden by frontity.settings.js but I’m not clear how, and if however it is doing it is preventing me from aliasing and including my theme colors the way that I want to.

Does this make sense?

Hey @thedonquixotic :wave:

Okay, I think you touched upon a couple of problems and I ll try to unpack them all.

1. importing the theme

I notice that in your example, you had a commented out line:

//const primaryColor = desertJackalope.theme.colors.primary.default;

as you have correctly indicated later in your post, you are missing the state, so the whole line should be:

const primaryColor = desertJackalope.state.theme.colors.primary.default;

With that fixed, you should be able to use import the index.js and use the values defined there.

CSS custom properties.

All that being said, what is your use case for CSS custom properties?

The variables that you define in state.theme in the src/index.js file can be accessed in any component as long as it’s wrapped with connect(). So, this essentially serves the same function as CSS Custom Properties.

In fact, you have already used that functionality by using the isBlog property in your src/components/Archive/archive.js

As far as I can tell, you don’t have to do any prop drilling because as long as you wrap your component with connect(), your theme variables should be available in that component in state.theme. :slight_smile:

Addiionally, not that there are some caveats with using <Global/>, most notably that Frontity is not able to optimise the CSS that is provided in it. More info here

Hope this helps!

1 Like

Thank you for the excellent response!

Correcint it to desertJackalope.state.theme.colors.primary.default; doesn’t fix the issue unfortunately.

Here’s what I added:

import desertJackalope from "../index";
const primeColor = desertJackalope.state.theme.colors.primary.default;

(it says primeColor so that I could just swap out variable name without having to unimport the other variables)

here’s the output I get:

from console:

SERVER STARTED -- Listening @ http://localhost:3000
  - mode: development
  - target: module


webpack built client 4ab813001e2039c4a137 in 8346ms
ℹ 「wdm」: Child client:
                     Asset      Size        Chunks             Chunk Names
         archive.module.js  64.9 KiB       archive  [emitted]  archive    
    server-front.module.js  5.75 MiB  server-front  [emitted]  server-front
     + 1 hidden asset
Child server:
        Asset      Size  Chunks             Chunk Names
    server.js  8.43 MiB    main  [emitted]  main
ℹ 「wdm」: Compiled successfully.
TypeError: Cannot read property 'state' of undefined
    at eval (webpack-internal:///./packages/desert-jackalope/src/components/index.js:19:343)
    at Module../packages/desert-jackalope/src/components/index.js (/mnt/c/Users/aslan/home/work/server-front/build/server.js:5396:1)
    at __webpack_require__ (/mnt/c/Users/aslan/home/work/server-front/build/server.js:27:30)
    at eval (webpack-internal:///./packages/desert-jackalope/src/index.js:2:69)
    at Module../packages/desert-jackalope/src/index.js (/mnt/c/Users/aslan/home/work/server-front/build/server.js:5540:1)
    at __webpack_require__ (/mnt/c/Users/aslan/home/work/server-front/build/server.js:27:30)
    at eval (webpack-internal:///./build/bundling/entry-points/server.ts:3:84)
    at Module../build/bundling/entry-points/server.ts (/mnt/c/Users/aslan/home/work/server-front/build/server.js:139:1)
    at __webpack_require__ (/mnt/c/Users/aslan/home/work/server-front/build/server.js:27:30)
    at /mnt/c/Users/aslan/home/work/server-front/build/server.js:104:18
TypeError: Cannot read property 'state' of undefined
    at eval (webpack-internal:///./packages/desert-jackalope/src/components/index.js:19:343)
    at Module../packages/desert-jackalope/src/components/index.js (/mnt/c/Users/aslan/home/work/server-front/build/server.js:5396:1)
    at __webpack_require__ (/mnt/c/Users/aslan/home/work/server-front/build/server.js:27:30)
    at eval (webpack-internal:///./packages/desert-jackalope/src/index.js:2:69)
    at Module../packages/desert-jackalope/src/index.js (/mnt/c/Users/aslan/home/work/server-front/build/server.js:5540:1)
    at __webpack_require__ (/mnt/c/Users/aslan/home/work/server-front/build/server.js:27:30)
    at eval (webpack-internal:///./build/bundling/entry-points/server.ts:3:84)
    at Module../build/bundling/entry-points/server.ts (/mnt/c/Users/aslan/home/work/server-front/build/server.js:139:1)
    at __webpack_require__ (/mnt/c/Users/aslan/home/work/server-front/build/server.js:27:30)
    at /mnt/c/Users/aslan/home/work/server-front/build/server.js:104:18

from browser response

TypeError: Cannot read property 'state' of undefined
    at eval (webpack-internal:///./packages/desert-jackalope/src/components/index.js:19:343)
    at Module../packages/desert-jackalope/src/components/index.js (/mnt/c/Users/aslan/home/work/server-front/build/server.js:5396:1)
    at __webpack_require__ (/mnt/c/Users/aslan/home/work/server-front/build/server.js:27:30)
    at eval (webpack-internal:///./packages/desert-jackalope/src/index.js:2:69)
    at Module../packages/desert-jackalope/src/index.js (/mnt/c/Users/aslan/home/work/server-front/build/server.js:5540:1)
    at __webpack_require__ (/mnt/c/Users/aslan/home/work/server-front/build/server.js:27:30)
    at eval (webpack-internal:///./build/bundling/entry-points/server.ts:3:84)
    at Module../build/bundling/entry-points/server.ts (/mnt/c/Users/aslan/home/work/server-front/build/server.js:139:1)
    at __webpack_require__ (/mnt/c/Users/aslan/home/work/server-front/build/server.js:27:30)
    at /mnt/c/Users/aslan/home/work/server-front/build/server.js:104:18

CSS Custom Properties

My reason for using CSS custom properties is informed by a philosophy of relying on the native browser implementations as much as possible.

Global

Yeah I’m aware of those issues. I’ve tried to keep the global CSS as small as possible. It basically holds just the root variables.

I see. It seems like the issue you are having has to do with an incorrect import path.

However, I would strongly recommend not to import this package directly. The main reason is that the data will not be reactive:

Normally, if you update the value of colors.primary in some component, frontity will re-render all the components that use that variable if you pass it down from state.theme. If you just import it directly, frontity has no knowledge of that and won’t rerender.

I see. I would not worry about the particular features being “native” or not. Using the variables defined in JS gives you the same benefits. Passing them again into CSS Variables is just extra work. There is no significant difference in performance and CSS variables won’t work in IE11 and below!

So, in your case, you should just wrap your components with connect() and if you want to pass state to your CSS you can do one of:

1. Create a function that gets state and returns the CSS:

const MyComp = ({ state }) => (
  <>
    <Global styles={globalStyles(state)} />
    <div css={someStyle(state)}>...</div>
  </>
);

const globalStyles = state => css`
  body {
    color: ${state.theme.colors.primary};
  }
`;

const someStyle = state => css`
  color: ${state.theme.colors.primary};
`;

2. Define the CSS inside the component:

const MyComp = ({ state }) => {
  const globalStyles = css`
    body {
      color: ${state.theme.colors.primary};
    }
  `;
  
  const someStyle = css`
    color: ${state.theme.colors.primary};
  `;

  return (
    <>
      <Global styles={globalStyles} />
      <div css={someStyle}>...</div>
    </>
  );
};

3. Pass down props to styled components (not possible with Global):

const MyComp = ({ state }) => (
  <>
    <Global styles={globalStyles(state)} />
    <Div primary={state.theme.colors.primary}>...</Div>
  </>
);

const globalStyles = state => css`
  body {
    color: ${state.theme.colors.primary};
  }
`;

const Div = styled.div`
  color: ${props => props.primary};
`;

Sorry, I don’t think so. I’ve double checked my import path and it is correct.

Also I don’t need the data to be reactive. It’s intended to be static variables used to define the visual theming.

Are you saying though that if the server settings in frontity.settings.js file changes those variables that they won’t change?

I’m not sure what you are referring to exactly, but I think that this is not what I meant. I was only referring to the re-rendering of components on the client.

The solution that I suggested is the preferred way to accomplish what you are trying to do even if you “don’t need it to be reactive” right now, because:

  1. you might want some part of to be reactive in the future.
  2. this way you don’t have to import your variables in every file that uses them, just use the state prop
  3. you are already using the same mechanism for non-style related settings and actions, so why use a different one just for styles? :upside_down_face:

Hope this helps!

What I was referring to was this:
Settings which are established or changed in src/index.js mirror settings that can be written in frontity.settings.js. src/index.js establishes default values that then can be overwritten on a per server basis via frontity.settings.js.

So for instance, by default my theme has set desertJackalope.state.theme.isBlog to “false”, but if I install my theme on a server and in frontity.settings.js set state.theme.isBlog to true then those settings will override it.

This isn’t the point though. Maybe I can do things one way or another but whether or not I’m using custom CSS properties isn’t the issue. The issue is that I’ am not being able to access the state from outside of a component. I can access isBlog blog from inside the component but if I want to access a key-value held in state as a variable that I use in my CSS styling I can’t. The way that @Segun accesses Chakra UI theme’s key-values for colors is through the use of prop drilling which I’d like to avoid.

So what I’m trying to figure out is how can I set color values in my src/index.js file and access those key-values from outside of components that are directly inheriting state?

Let me give you some simplified examples I’ve tried and their results:

Example 1 (export variable from src/index.js file)

src/index.js

import Theme from "./components";
import image from "@frontity/html2react/processors/image";

const desertJackalope = {
  name: "desert-jackalope",
  roots: {
    // In Frontity, any package can add React components to the site.
    // We use roots for that, scoped to the "theme" namespace.
    theme: Theme
  },
  state: {
    // State is where the packages store their default settings and other
    // relevant state. It is scoped to the "theme" namespace.
    theme: {
      isBlog: false,
      colors: {
        primary: {
          default: "#2657eb",
          heavy: "#1f38c5"
        }
      },
      newsletterURL:
        "https://the-jackalope.us8.list-manage.com/subscribe/post?u=7fc8ae244460f6dd1c74dd7bf&amp;id=74ff6c880b",
      footerlinks: {
        github: "https://github.com/jcklpe",
        blog: "https://www.jackalope.tech"
      }
    }
  },
  // Actions are functions that modify the state or deal with other parts of
  // Frontity like libraries.
  actions: {
    theme: {}
  },
  libraries: {
    html2react: {
      // Add a processor to html2react so it processes the <img> tags
      // inside the content HTML. You can add your own processors too.
      processors: [image]
    }
  }
};

const primeColor = desertJackalope.state.theme.colors.primary.default;

export { primeColor };

export default desertJackalope;


src/component/index.js

import React from "react";
import { css, connect, styled } from "frontity";

const Theme = ({ state }) => {
  return (
    <section>
      <TestComponent> testing</TestComponent>
      <p>testing</p>
    </section>
  );
};

export default connect(Theme);

//- CSS

const TestComponent = styled.p`
 background: ${primeColor};
`;


resulting error:

ReferenceError: primeColor is not defined
    at eval (webpack-internal:///./packages/desert-jackalope/src/components/index.js:9:147)
    at Module../packages/desert-jackalope/src/components/index.js (/mnt/c/Users/aslan/home/work/server-front/build/server.js:4912:1)
    at __webpack_require__ (/mnt/c/Users/aslan/home/work/server-front/build/server.js:27:30)
    at eval (webpack-internal:///./packages/desert-jackalope/src/index.js:3:69)
    at Module../packages/desert-jackalope/src/index.js (/mnt/c/Users/aslan/home/work/server-front/build/server.js:4924:1)
    at __webpack_require__ (/mnt/c/Users/aslan/home/work/server-front/build/server.js:27:30)
    at eval (webpack-internal:///./build/bundling/entry-points/server.ts:3:84)
    at Module../build/bundling/entry-points/server.ts (/mnt/c/Users/aslan/home/work/server-front/build/server.js:139:1)
    at __webpack_require__ (/mnt/c/Users/aslan/home/work/server-front/build/server.js:27:30)
    at /mnt/c/Users/aslan/home/work/server-front/build/server.js:104:18

Example 2 (export primeColor using connect() )

Okay this is me trying something new based on what you’ve said. But this also does not work. It is able to compile but it does not result in the intended styling:

src/index.js

import Theme from "./components";
import image from "@frontity/html2react/processors/image";
import { css, connect, styled } from "frontity";

const desertJackalope = {
  name: "desert-jackalope",
  roots: {
    // In Frontity, any package can add React components to the site.
    // We use roots for that, scoped to the "theme" namespace.
    theme: Theme
  },
  state: {
    // State is where the packages store their default settings and other
    // relevant state. It is scoped to the "theme" namespace.
    theme: {
      isBlog: false,
      colors: {
        primary: {
          default: "#2657eb",
          heavy: "#1f38c5"
        }
      },
      newsletterURL:
        "https://the-jackalope.us8.list-manage.com/subscribe/post?u=7fc8ae244460f6dd1c74dd7bf&amp;id=74ff6c880b",
      footerlinks: {
        github: "https://github.com/jcklpe",
        blog: "https://www.jackalope.tech"
      }
    }
  },
  // Actions are functions that modify the state or deal with other parts of
  // Frontity like libraries.
  actions: {
    theme: {}
  },
  libraries: {
    html2react: {
      // Add a processor to html2react so it processes the <img> tags
      // inside the content HTML. You can add your own processors too.
      processors: [image]
    }
  }
};

const primeColor = desertJackalope.state.theme.colors.primary.default;

export default connect(desertJackalope, primeColor);


the src/components/index file:

import React from "react";
import { css, connect, styled } from "frontity";

import { primeColor } from "../index";

const Theme = ({ state }) => {
  return (
    <section>
      <TestComponent> testing ing</TestComponent>
      <p>testing</p>
    </section>
  );
};

export default connect(Theme);

//- CSS

const TestComponent = styled.p`
  background: ${primeColor};
`;


Result
This doesn’t throw an error but it also doesn’t apply the styling at all.


Example 3 (export desertJackalope theme via connect)

The src/index file:

import Theme from "./components";
import image from "@frontity/html2react/processors/image";
import { css, connect, styled } from "frontity";

const desertJackalope = {
  name: "desert-jackalope",
  roots: {
    // In Frontity, any package can add React components to the site.
    // We use roots for that, scoped to the "theme" namespace.
    theme: Theme
  },
  state: {
    // State is where the packages store their default settings and other
    // relevant state. It is scoped to the "theme" namespace.
    theme: {
      isBlog: false,
      colors: {
        primary: {
          default: "#2657eb",
          heavy: "#1f38c5"
        }
      },
      newsletterURL:
        "https://the-jackalope.us8.list-manage.com/subscribe/post?u=7fc8ae244460f6dd1c74dd7bf&amp;id=74ff6c880b",
      footerlinks: {
        github: "https://github.com/jcklpe",
        blog: "https://www.jackalope.tech"
      }
    }
  },
  // Actions are functions that modify the state or deal with other parts of
  // Frontity like libraries.
  actions: {
    theme: {}
  },
  libraries: {
    html2react: {
      // Add a processor to html2react so it processes the <img> tags
      // inside the content HTML. You can add your own processors too.
      processors: [image]
    }
  }
};

export default connect(desertJackalope);

the src/component/index file:

import React from "react";
import { css, connect, styled } from "frontity";

import { desertJackalope } from "../index";

const primeColor = desertJackalope.state.theme.colors.primary.default;

const Theme = ({ state }) => {
  return (
    <section>
      <TestComponent> testing ing</TestComponent>
      <p>testing</p>
      <p>{primeColor}</p>
    </section>
  );
};

export default connect(Theme);

//- CSS

const TestComponent = styled.p`
  background: ${primeColor};
`;


Results in:

TypeError: Cannot read property 'state' of undefined
    at eval (webpack-internal:///./packages/desert-jackalope/src/components/index.js:9:73)
    at Module../packages/desert-jackalope/src/components/index.js (/mnt/c/Users/aslan/home/work/server-front/build/server.js:4912:1)
    at __webpack_require__ (/mnt/c/Users/aslan/home/work/server-front/build/server.js:27:30)
    at eval (webpack-internal:///./packages/desert-jackalope/src/index.js:2:69)
    at Module../packages/desert-jackalope/src/index.js (/mnt/c/Users/aslan/home/work/server-front/build/server.js:4924:1)
    at __webpack_require__ (/mnt/c/Users/aslan/home/work/server-front/build/server.js:27:30)
    at eval (webpack-internal:///./build/bundling/entry-points/server.ts:3:84)
    at Module../build/bundling/entry-points/server.ts (/mnt/c/Users/aslan/home/work/server-front/build/server.js:139:1)
    at __webpack_require__ (/mnt/c/Users/aslan/home/work/server-front/build/server.js:27:30)
    at /mnt/c/Users/aslan/home/work/server-front/build/server.js:104:18

Does this make sense? Do you see what I’m asking how to do?

Possibly related issue here: Double rendering of items held in state array?

Am I misunderstanding how themes/settings work with state for Frontity?

Yes, I see your problem! :slight_smile:

I agree that this should perhaps be better documented in the docs!

There are 2 ways that you can achieve what you are looking for:

The first one is the way that I mentioned already here:

3. Pass down props to styled components (not possible with Global):

const MyComp = ({ state }) => (
  <>
    <Global styles={globalStyles(state)} />
    <Div primary={state.theme.colors.primary}>...</Div>
  </>
);

const globalStyles = state => css`
  body {
    color: ${state.theme.colors.primary};
  }
`;

const Div = styled.div`
  color: ${props => props.primary};
`;

But also, instead of using a styled-component, you can use the css prop and create a function that takes state as a parameter and returns some styles. This way you can use it anywhere.

What I don’t really understand is what use case you have accesing it “outside of components”. In every one of your examples, you can pass the state.theme.colors.primary.default as a prop to your styled-component like:

const Theme = ({ state }) => {
  return (
    <section>
      <TestComponent color={state.theme.colors.primary.default}> testing</TestComponent>
      <p>testing</p>
    </section>
  );
};

const TestComponent = styled.p`
 background: ${({ color }) => color};
`;

The problem with these solutions is that they all saving the values as props, which means relying on prop drilling similar to how Chakra UI’s theme did stuff. You can see an example of how they did that with bg={primary.700} on this component [here].

I don’t want to use a prop drilling approach. If for nothing else because I think it junks up the markup with styling information which I think should remain as separate as possible (in order to be as easily encapsulated as possible). Maybe this isn’t the “react way” or “frontity way” of doing things but I’m asking here for suggestions on how to do this. If it’s not possible then I’ll just have to deal with my current solution which is not the best, but is what it is.


I’m not sure I understand why state is returning undefined but I’m guessing it has something to do with the way stuff is merged. I hit a bug in that other thread I linked that has to do with a problem merging arrays from frontity.settings.js and src/index.js

If this is the source of the problem, then is it something that I can work around or that can be changed? I understand if frontity doesn’t want to support my preferred method of encapsulating styles. That’s totally fine and ya’ll’s right. Currently, I am defining colors in my src/theme.js file instead of in the src/index.js file,and therefore I sacrifice the ability to change color scheme variables in frontity.settings.js for the sake of adhering to my preferred project structure and markup styling. I’m okay making that tradeoff. I’d prefer to not have to, but I’m also not demanding ya’ll conform your project to my preferences, nor am I going to start using props to pass along variables.

To be honest I still don’t see the issue that you’re concerned with :slightly_smiling_face:

There is no “prop drilling” in any of the solutions I posted nor in the Chakra component that you linked to. You can props to pass inline styles to Chakra components like

<DrawerContent bg="primary.700" px={8} max="auto">

This is intentional and there is nothing wrong with that :slightly_smiling_face:

I would suggest that you try the approach that I outlined and see how you like it. I know that it’s quite a bit different from doing “classic” CSS and takes some getting used to.

Why do you think that? :slight_smile:

Honestly it’s an aesthetic preference on my part. There’s nothing wrong with setting a bunch of styles as props in a component’s markup but I would prefer to not do that because of separation of concerns. This could be my Wordpress background speaking. I like my mark up and my styling as separate as possible.

I realize that in the React community and espc in the styled components community this style preference isn’t shared as much, but I’d like to know how to do it my preferred way, if it is at all possible to do it that way.

There is no “prop drilling” in any of the solutions I posted nor in the Chakra component that you linked to. You can props to pass inline styles to Chakra components like

Sorry yes you are correct. I realize the distinction you are making. Since the color settings are in the top level state, they can be accessed from any lower level components and passed as props.

BUT,

my issue is I don’t want to be passing my styling as props.

Why?

As said above, because I think looks bad. I think it makes it harder to read and I prefer to keep my styles as separate from mark up as possible.

I understand some of the advantages of CSS-in-JS but I don’t see any advantage in mixing my CSS variables with my markup syntax.

I’m not asking ya’ll to share or approve of my preferred approach to this. I’m simply asking is it possible? If it’s not possible, that’s cool, and ya’ll don’t have to support stuff that ya’ll don’t want to in your framework. I get that. I’m not asking these questions because I’m trying to boss you around. I’m asking because I want to know is it possible?

Can I get a straight answer on that? I’m not asking for code style advice, I’m asking a technical question that should have a yes or no answer.

Sorry, I didn’t have time to read the whole thread in detail but I wanted to add one clarification: Frontity state is meant to be imported through connect. No other way is going to be supported.

Said that, you don’t need to store all your values in the state, you can create a theme.js file with a plain object and import those values in your files.

// src/theme.js
const theme = {
  colors...
};

export default theme;
import theme from "./theme";

const MyComp = () => (
  <SomeStyledComp color={theme.colors.primary...} />
);

You can even import that in your state if you want to have it in both places

// src/index.js
import theme from "./theme";

export default {
 state: {
   theme: {
     ...theme,
     // ...
    }
  }
}

But remember that if you import it directly from theme.js the things you add in frontity.settings.js will be obviously ignored.

Again, please accept my apologies if this doesn’t make sense. As I said, it’s a very long topic and I didn’t have to read it in detail.

1 Like

No worries and thank you Luis!

The first technique you mention, creating a theme.js file and then importing it into my src/components/index.js file is currently how I’m dealing with the issue. It’s works well enough except for the fact that frontity.settings.js does not overwrite or modify variables.

The second technique though as pictured here:

// src/index.js
import theme from "./theme";

export default {
 state: {
   theme: {
     ...theme,
     // ...
    }
  }
}

is resulting in the the following error: TypeError: Cannot read property 'router' of undefined.

For documentation purposes here are the relevant files:

src/index.js

import Theme from "./components";
import image from "@frontity/html2react/processors/image";
import { css, connect, styled } from "frontity";
import colors from "./theme";

const desertJackalope = {
  name: "desert-jackalope",
  roots: {
    // In Frontity, any package can add React components to the site.
    // We use roots for that, scoped to the "theme" namespace.
    theme: Theme
  },
  state: {
    // State is where the packages store their default settings and other
    // relevant state. It is scoped to the "theme" namespace.
    theme: {
      ...colors,
      isBlog: false,
      newsletterURL:
        "https://the-jackalope.us8.list-manage.com/subscribe/post?u=7fc8ae244460f6dd1c74dd7bf&amp;id=74ff6c880b",
      footerlinks: [
        { name: "github", href: "https://github.com/jcklpe" },
        { name: "blog", href: "https://www.jackalope.tech" }
      ]
    }
  },
  // Actions are functions that modify the state or deal with other parts of
  // Frontity like libraries.
  actions: {
    theme: {}
  },
  libraries: {
    html2react: {
      // Add a processor to html2react so it processes the <img> tags
      // inside the content HTML. You can add your own processors too.
      processors: [image]
    }
  }
};

export default desertJackalope;



src/components/index.js (chunks of this file have been removed for concision but are otherwise unnecessary for troubleshooting this issue)

import React from "react";
import { Global, css, connect, styled, router } from "frontity";
import Header from "./Header";

const Theme = ({ state }) => {

  return (
    <>
      <Meta />
      <Global styles={globalStyles} />
      <Header />

      
             <Absolute>

... the contents of this component have been removed for concision
            </Absolute>
          </animated.div>
        );
      })}
    </>
  );
};

export default connect(Theme);

//- GLOBAL STYLES CSS

// set global styles
const globalStyles = state => css`


  :root {
    --primary-heavy: purple;
    --primary: blue;
    --snappy: cubic-bezier(0.075, 0.82, 0.165, 1);
    --heavy-snap: cubic-bezier(0.6, -0.28, 0.735, 0.045);
    --accent: green;
    --dark: black;
    --dark90:black;
    --dark80: black;
    --dark30: black;
}
`;

//- Page Transition stuff

const Absolute = styled.div`
  position: absolute;
  width: 100%;

`;

What’s the content of theme.js?

Sorry should have included that:

const colors = {
  primary: {
    default: "#2657eb",
    heavy: "#1f38c5"
  },
  dark: {
    100: "rgba(12, 17, 43, 1)",
    90: "rgba(12, 17, 43, 0.9)",
    80: "rgba(12, 17, 43, 0.8)",
    30: "rgba(12, 17, 43, 0.3)"
  },
  accent: "#7200ff"
};


//- Exports
export default colors

Is it possible that you are using router someplace where it can result in the
TypeError: Cannot read property 'router' of undefined. ?

There is no router exported from frontity, so this line is incorrect:

import { Global, css, connect, styled, router } from "frontity";

Sorry, I just added that because I was getting an error of “router is undefined” after updating the frontity framework to the newest version. It was not in my code previously though when I was getting the above errors. Things are a bit of a mess, but I’m going to try to revert my code to a previous commit on a fresh server so that I can reproduce the original problem.

Can anyone confirm in the meantime whether the expected/intended behavior of ...theme in src/index.js will allow for values to be overwritten by frontity.settings.js?

I think that you are talking about the line with ...colors, correct?

Yes, as far as I can tell, this should work as expected.

As for this:

It’s hard to debug without looking at the full stack trace, but I think you should be able to follow the filenames and lines in the full error message to find the place where .router is being accessed in your code and it’s most likely the culprit :slight_smile:

I fixed my local install and did some more dev work today. From what I can tell I can use the ...colors to include parts of theme.js file inside of the src/index.js file, but it doesn’t allow for those settings to get overwritten by the frontity.settings.js files. So… that’s not ideal but I guess it’s just a limitation of the framework I gotta live with.