Switchable custom bootstrap themes with sass - css

I was able to find many different approaches on how to make a switchable themes in SASS I wasn't able to figure out how can I apply that approach to bootstrap overrides. Let's say I want to have light and dark theme and both themes would override bootstraps colors differently.
// Light-theme
$theme-colors: (
"primary": #8A2F4F
);
// Dark-theme
$theme-colors: (
"primary": #fff
);
In the example above are overrides for primary of theme-colors. How can I conditionally set them based on theme the user has selected?

Well,
there are many ways to do this. I'd suggest you to generate multiple CSS files based on the _variables.scss file. All you have to do is build new theme with different variable file. You can check this method in my public repo.
Other way would be to use CSS custom variables (called as custom properties). You can do something like this. I've just copy pasted and altered the CSS. It is just to give an idea.
in sass file,
$theme-colors: (
"primary": var(--primary-color);
);
and in other variable file,
element.dark {
--primary-color: black;
}
element.light {
--primary-color: white;
}
These are two method I would suggest.

Here is how I did it with React, Bootstrap and SASS:
In an index.scss file:
div#blue {
#import './blue.scss';
}
div#red {
#import './red.scss';
}
// The rest of bootstrap
// Seems to be necessary to import twice to get fonts to work
#import "node_modules/bootstrap/scss/bootstrap";
Then red.scss I over-ride Boostrap where needed:
$theme-colors: (
"primary": red,
);
// The rest of bootstrap
#import "node_modules/bootstrap/scss/bootstrap";
blue.scss is similar.
Finally my top level React component:
import './index.css'
import * as React from 'react'
import Button from 'react-bootstrap/Button'
export const Container = () => {
const [color, setColor] = useState('red');
const buttonClick = () => {
if (color === 'red') setColor('blue')
if (color === 'blue') setColor('red')
}
return (
<div id={theme} >
Switch themes...
<Button variant="primary" onClick={buttonClick}>Button</Button>
</div>
)
}
Press the button and it will change from red to blue. I am guessing that a className rather than a div selector is a better way to do this - but this approach is working for me so I am happy. Not sure how robust this solution is in complex applications yet.

Related

Selector ":root" is not pure (pure selectors must contain at least one local class or id) - NextJS with SASS modules

I've recently been switching to using modules in my next.js project, but I keep receiving this error in my newly created .module.scss files: "Selector ":root" is not pure (pure selectors must contain at least one local class or id)". I know this is because I'm not using pure css selectors as I've seen elsewhere online, and the only problem is the imports that I'm using, but I need those imports for variables like $cl-light-gray as seen below in this example file:
#import "src/common/styles/global-styles.scss";
#import "node_modules/bootstrap/scss/bootstrap";
#import "src/common/styles/palette.scss";
#import "src/common/styles/typography.scss";
.dashboard-dropdown-hover {
#extend .px-1;
#extend .py-2;
#extend .mt-3;
border: 1px solid transparent;
border-radius: 8px;
transition: 200ms;
background-color: transparent;
}
.dashboard-dropdown-hover:hover {
background-color: $cl-light-gray;
}
Does anyone have a solution to how I should fix this import problem? I know that if I switch back to .scss it will work, but I'm trying to avoid importing all the .scss files in _app.tsx because that would be at least 30 imports and also these styles aren't intended to be global. Lastly, why does Next.js expect me to use pure css selectors when I'm using Sass, which is used because of its non-pure elements?
After scouring the internet for a few hours I found a great solution from here: https://dhanrajsp.me/snippets/customize-css-loader-options-in-nextjs
EDIT: If you're using Next.js 12, check the bottom of the article above, because the solution is a little different.
You'll want to change your next.config.js file to include the following:
/** #type {import('next').NextConfig} */
require("dotenv").config();
const regexEqual = (x, y) => {
return (
x instanceof RegExp &&
y instanceof RegExp &&
x.source === y.source &&
x.global === y.global &&
x.ignoreCase === y.ignoreCase &&
x.multiline === y.multiline
);
};
// Overrides for css-loader plugin
function cssLoaderOptions(modules) {
const { getLocalIdent, ...others } = modules; // Need to delete getLocalIdent else localIdentName doesn't work
return {
...others,
localIdentName: "[hash:base64:6]",
exportLocalsConvention: "camelCaseOnly",
mode: "local",
};
}
module.exports = {
webpack: (config) => {
const oneOf = config.module.rules.find(
(rule) => typeof rule.oneOf === "object"
);
if (oneOf) {
// Find the module which targets *.scss|*.sass files
const moduleSassRule = oneOf.oneOf.find((rule) =>
regexEqual(rule.test, /\.module\.(scss|sass)$/)
);
if (moduleSassRule) {
// Get the config object for css-loader plugin
const cssLoader = moduleSassRule.use.find(({ loader }) =>
loader.includes("css-loader")
);
if (cssLoader) {
cssLoader.options = {
...cssLoader.options,
modules: cssLoaderOptions(cssLoader.options.modules),
};
}
}
}
return config;
},
};
I'm not seasoned with webpack or how it exactly works, but this solution worked for me. You can also change the regex to include css by doing (scss|sass|css) if you want.
As pointed out here, there is another option: you can import those styles in the global.css file. If you do that, Nextjs will be happy.
Any global styles (e.g., :root or any HTML elements/CSS classes that you want to have the same style absolutely everywhere in your app) should be placed into a global CSS file that you import into _app.js (which you just can add to the root folder of your project, if it doesn't already exist).
This global CSS file is also where you want to import any fonts that you will use app-wide.
Step-by-step instructions here: https://nextjs.org/docs/basic-features/built-in-css-support
In my particular case i was having the same headache with that issue, and was because i was trying to import the file with the path:
/node_modules/bootstrap/scss/bootstrap-utilities.scss
and that file was importing another file called _root.scss which was defined a selector in this style.
:root{
}
for solution that error i simply import the specific files used for my requirements
Another resources could help you:
https://www.youtube.com/watch?v=dOnYNEXv9BM&t=1044s
https://sass-lang.com/documentation/modules
https://dev.to/mr_ali3n/use-forward-in-sass-2bab

Why My custom colors are not working in my Angular material theme?

I trying to add more color to my angular material theme, I've added the success color in my style.scss by the map.deep-merge fucntion
// Custom Theming for Angular Material
// For more information: https://material.angular.io/guide/theming
#use '#angular/material' as material;
#use "sass:map";
#include material.core();
$custom-primary: material.define-palette(material.$light-blue-palette);
$custom-accent: material.define-palette(material.$blue-palette, A200, A100, A400);
$custom-warn: material.define-palette(material.$red-palette);
// extra Colors
$custom-success: material.define-palette(material.$green-palette);
$custom-danger: material.define-palette(material.$orange-palette);
$custom-theme: material.define-light-theme((
color: (
primary: $custom-primary,
accent: $custom-accent,
warn: $custom-warn,
)
));
$custom-theme: map.deep-merge(
$custom-theme,(
success: $custom-success,
danger: $custom-danger,
color:(
success: $custom-success,
danger: $custom-danger
)
)
);
#include material.all-component-themes($custom-theme);
Everything compiles correctly but when I try to apply the color to a button It looks like this
and don't have any idea why.
<button mat-raised-button color="success">Success</button>
<button mat-raised-button color="danger">Danger</button>
Curremtly I'm using "#angular/material": "^13.2.4",
I think only one step was missing: Adding .mat-success and .mat-danger CSS classes:
.mat-success {
background-color: material.get-color-from-palette($custom-success, 500);
color: material.get-color-from-palette($custom-success, 500-contrast);
}
.mat-danger {
background-color: material.get-color-from-palette($custom-danger, 500);
color: material.get-color-from-palette($custom-danger, 500-contrast);
}
I also removed the map.deep-merge, which was not necessary in this solution.
Complete theme.scss file:
// Custom Theming for Angular Material
// For more information: https://material.angular.io/guide/theming
#use "#angular/material" as material;
#include material.core();
$app-primary: material.define-palette(material.$purple-palette);
$app-accent: material.define-palette(material.$teal-palette, A200);
$app-warn: material.define-palette(material.$red-palette);
// extra Colors
$custom-success: material.define-palette(material.$green-palette);
$custom-danger: material.define-palette(material.$orange-palette);
$custom-theme: material.define-light-theme(
(
color: (
primary: $app-primary,
accent: $app-accent,
warn: $app-warn,
),
)
);
#include material.all-component-themes($custom-theme);
.mat-success {
background-color: material.get-color-from-palette($custom-success, 500);
color: material.get-color-from-palette($custom-success, 500-contrast);
}
.mat-danger {
background-color: material.get-color-from-palette($custom-danger, 500);
color: material.get-color-from-palette($custom-danger, 500-contrast);
}
Github Demo Link (unfortunately Stacklitz has problems with the custom theme)
if you used angular-CLI while building the project:
as per the official docs -
If you are using the Angular CLI, support for compiling Sass to CSS is
built-in; you only have to add a new entry to the "styles" list in
angular-cli.json pointing to the theme file (e.g.,
custom-theme.scss).
So make sure your theme is included in your angular.json and the necessary imports exist in the app.module.ts, and now you can import your theme in every file like this: #import YOUR_FILE_NAME After editing the angular.json file, you will need to run ng-serve again.
if you are not using angular-CLI:
as per the official docs -
If you are not using the Angular CLI, you can include a prebuilt theme
via a element in your index.html.
so first make sure that all the imports are correct
if neither helped:
add another import in each of the component files where mat* components are used
import { MatButtonModule } from "#angular/material/button";
Remember that when you add that import, your IDE will tell you (by greying it out) that it's not needed and can be removed.
if even that didn't help
As per latest angular material documentation you have to import modules from its respective package instead of #angular/material/
So change from:
import { MatToolbarModule , MatButtonModule, MatIconModule} from '#angular/material';
to:
import { MatToolbarModule } from '#angular/material/toolbar';
import { MatButtonModule } from '#angular/material/button';
import { MatIconModule } from '#angular/material/icon';
last resort
try building a minimal reproduction of an app using the same theme you built and post it here or try debuging it yourself (you'll be surprised by how much it can help)
and in any case after each change, if nothing happens, run ng-serve

How to extend a variable's map in SCSS?

I am using a package called Buefy which is a Vue.js wrapper for the Bulma CSS framework library. Buefy puts an attribute/property on its template components called type (e.g., type="is-warning"). As per the Buefy documentation, the term "is-warning" is pre-defined according to the $colors variable set within SCSS:
So I followed these instructions from Buefy to be able to customize the the $colors map, but what I would like to do, however, is keep the initial SCSS defined in those instructions within a single base.scss file, and then extend with my own customized SCSS file.
I have included the base.scss file in my Vue project with the following snippet in the vue.config.js file:
module.exports = {
css: {
loaderOptions: {
sass: {
prependData: `
#import "#/assets/scss/base.scss";
`
}
}
}
}
So my question is, how can I extend the $colors map in the base.scss with my own names and values?
This is a snippet from base.scss, where the $colors map is defined:
```css
$colors: (
"white": ($white, $black),
"black": ($black, $white),
"light": ($light, $light-invert),
"dark": ($dark, $dark-invert),
"primary": ($primary, $primary-invert),
"info": ($info, $info-invert),
"success": ($success, $success-invert),
"warning": ($warning, $warning-invert),
"danger": ($danger, $danger-invert),
"twitter": ($twitter, $twitter-invert)
);
```
So, again, my question is how can I extend the $colors map in a different file outside of base.scss (perhaps in App.vue) to leave the original untouched? I didn't see any solution shown in the scss docs.
Your custom color values would go in a variable (later merged into $colors) called $custom-colors, and it's actually described in the docs on "derived variables".
And to customize it:
// Import Bulma's core
#import "~bulma/sass/utilities/_all";
// Set your colors
$custom-colors: (
"facebook": ($blue, $white),
"linkedin": ($dark-blue, $white)
// etc.
);
// Setup $colors to use as bulma classes (e.g. 'is-twitter')
$colors: (
"white": ($white, $black),
"black": ($black, $white),
"light": ($light, $light-invert),
"dark": ($dark, $dark-invert),
"primary": ($primary, $primary-invert),
"info": ($info, $info-invert),
"success": ($success, $success-invert),
"warning": ($warning, $warning-invert),
"danger": ($danger, $danger-invert),
"twitter": ($twitter, $twitter-invert)
);
// Import Bulma and Buefy styles
#import "~bulma";
#import "~buefy/src/scss/buefy";
It's internally using a custom function called mergeColorMaps.

Reference SCSS Variable Internally Within Component

I'm trying to reference a color variable from my main.scss file within my component but not sure what the correct syntax is. Right now it works by hard coding the color hex value.
I can make it work by adding an external stylsheet, and referencing the theme color that way however don't want to create an extra file but rather use internal styling.
Here's main.scs:
$theme-colors: (
'primary': #00a677,
);
Here's the component:
import React from 'react';
const box = {
color: '#9D2235',
};
const Box = () => {
return(
<div style={box}><h1>I'm A Box!</h1></div>
)
}
export default Box;
Any idea how I can do this?
What you can do is, put a class on the box component
In main.scss
$primary: #00a677
.box {
color: $primary
}
In your component
import React from 'react';
const Box = () => {
return(
<div className='box'><h1>I'm A Box!</h1></div>
)
}
export default Box;

conditional css in create-react-app

I have default css file and separate css file that should be applied (to owerride default) only when certain conditions are met.
I am using create-react-app wit default import 'file.css' syntax.
What is the best way forward to decide whether to load or not load particular css file dynamically?
The require method only worked in development (as all the CSS is bundled upon build), and the import method did not work at all (using CRA version 3.3).
In our case, we have multiple themes, which cannot be bundled - so we solved this using React.lazy and React.Suspense.
We have the ThemeSelector, which loads the correct css conditionally.
import React from 'react';
/**
* The theme components only imports it's theme CSS-file. These components are lazy
* loaded, to enable "code splitting" (in order to avoid the themes being bundled together)
*/
const Theme1 = React.lazy(() => import('./Theme1'));
const Theme2 = React.lazy(() => import('./Theme2'));
const ThemeSelector: React.FC = ({ children }) => (
<>
{/* Conditionally render theme, based on the current client context */}
<React.Suspense fallback={() => null}>
{shouldRenderTheme1 && <Theme1 />}
{shouldRenderTheme2 && <Theme2 />}
</React.Suspense>
{/* Render children immediately! */}
{children}
</>
);
export default ThemeSelector;
The Theme component's only job, is to import the correct css file:
import * as React from 'react';
// 👇 Only important line - as this component should be lazy-loaded,
// to enable code - splitting for this CSS.
import 'theme1.css';
const Theme1: React.FC = () => <></>;
export default Theme1;
The ThemeSelector should wrap the App component, in the src/index.tsx:
import React from 'react';
import ReactDOM from 'react-dom';
import ThemeSelector from 'themes/ThemeSelector';
ReactDOM.render(
<ThemeSelector>
<App />
</ThemeSelector>,
document.getElementById('root')
);
As I understand, this forces each Theme to be split into separate bundles (effectively also splitting CSS).
As mentioned in the comments, this solution does not present an easy way of switching themes runtime. This solution focuses on splitting themes into separate bundles.
If you already got themes split into separate CSS files, and you want to swap themes runtime, you might want to look at a solution using ReactHelmet (illustrated by #Alexander Ladonin's answer below)
You can use require('file.css') syntax instead. This will allow you to put it inside of a conditional.
e.g.
if(someCondition) {
require('file.css');
}
Use React Helmet. It adds links, meta tags etc into document header dynamically.
Add it into any render method.
import {Component} from 'react';
import ReactHelmet from 'react-helmet';
class Example extends Component{
render(
<ReactHelmet link={
[{"rel": "stylesheet", type:"text/css", "href": "/style.css"}]
}/>);
}
}
You can rewrite it on next <ReactHelmet/> rendering.
One simple solution that I found that works in production is to use vercel's styled-jsx. First, install styled-jsx:
npm install --save styled-jsx
Or if you use Yarn:
yarn add styled-jsx
Now create strings from your css file, so for instance:
const style1 = `
div {
display: flex;
flex-direction: column;
align-items: center;
}
`
const style2 = `
div {
display: flex;
flex-direction: column;
align-items: center;
}
`
And then in your React Component, you can do something like this:
const MyComponent = () => {
return (
<div className='my-component'>
<style jsx>
{
conditionA ? style1: style2
}
</style>
</div>
)
}
Simply add <style jsx>{your_css_string}</style> to the component which you wish to add styling to and you can then to implement conditions just use different strings to import different css styling.
If you are here you most likely are trying to condition a CSS or SCSS import, probably to make some light/dark mode theme or something. The accepted answer works just on mount, after the second css is loaded they are both loaded and you dont have a way to unload them, or actually you have, keep reading...
The use of React lazy and suspense is awesome but in this case we need to help our selves from webpack, because is actually the guy that bundles stuff and can also unbundle stuff, which is what you need, a toggle of css imports basically
Adding webpack lazyStyleTag
Go to your webpack config file and add the following rules
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
// Probly you already have this rule, add this line
exclude: /\.lazy\.css$/i,
use: ["style-loader", "css-loader"],
},
// And add this rule
{
test: /\.lazy\.css$/i,
use: [
{ loader: "style-loader", options: { injectType: "lazyStyleTag" } },
"css-loader",
],
},
],
},
};
Now take your CSS files and change their name to the lazy named convention
You probably have this
styles.css
// or
styles.min.css
Now will be this:
styles.lazy.css
Then create your React theme Provider in a simple React context, this context will wrap your App so it will load the conditioned CSS everytime the context state changes. This context state is going to be availabe anywhere inside your app as well as the setter via a custom hook we will export from the same file, check this out:
import React, {
useEffect, createContext, useState, useContext,
} from 'react';
import { Nullable } from 'types';
// Import both files here like this:
// Import of CSS file number 1
import LightMode from './theme/styles.lazy.css';
// Import of CSS file number 2
import DarkMode from './theme/styles.lazy.css';
interface IContext {
theme: Nullable<string>
toggleTheme: () => void
}
const Context = createContext<IContext>({
theme: null,
toggleTheme: () => { },
});
// Your Provider component that returns the Context.Provider
// Let's also play with the sessionStorage, so this state doesn't
// brake with browser refresh or logouts
const ThemeProvider: React.FC = ({ children }) => {
// Im initialazing here the state with any existing value in the
//sessionStorage, or not...
const [theme, setTheme] = useState<Nullable<string>>(sessionStorage.getItem('themeMode') || 'dark');
// this setter Fn we can pass down to anywhere
const toggleTheme = () => {
const newThemeValue = theme === 'dark' ? 'light' : 'dark';
setTheme(newThemeValue);
sessionStorage.setItem('themeMode', newThemeValue);
};
// Now the magic, this lazy css files you can use or unuse
// This is exactly what you need, import the CSS but also unimport
// the one you had imported before. An actual toggle of import in a
// dynamic way.. brought to you by webpack
useEffect(() => {
if (theme === 'light') {
DarkMode.unuse();
LightMode.use();
} else if (theme == 'dark') {
LightMode.unuse();
DarkMode.use();
}
}, [theme]);
return (
<Context.Provider value={{ theme, toggleTheme }}>
{children}
</Context.Provider>
);
};
export default ThemeProvider;
// This useTheme hook will give you the context anywhere to set the state of // theme and this will toggle the styles imported
export const useTheme = () => useContext(Context);
Remember to put this state on the sessionStorage like in this example so your user has the state available every time it comes back or refreshes the page
Don't forget to wrap the friking App with the Provider:
import ThemeProvider from './ThemeProvider'
const App = () => {
return (
<ThemeProvider>
<App />
</ThemeProvider>
)
}
Now just toggle the CSS imports of your application using your cool useTheme hook
import { useTheme } from './yourContextFile';
// inside your component
const AnyComponentDownTheTree = () => {
const { theme, toggleTheme } = useTheme()
// use the toggleTheme function to toggle and the theme actual value
// for your components, you might need disable something or set active a
// switch, etc, etc
}
Other solution does not work for me. After one day of the search, I obtain bellow solution. In my issue, I have two CSS files for RTL or LTR like app.rtl.css or app.ltr.css
Create a functional component Style like this:
import React, { useState } from "react";
export default function Style(props) {
const [stylePath, setStylePath] = useState(props.path);
return (
<div>
<link rel="stylesheet" type="text/css" href={stylePath} />
</div>
);
}
And then you can call it, for example in App.js:
function App() {
...
return (
<Style path={`/css/app.${direction}.css`} />
)}
direction param contains rtl or ltr and determine which file should be loaded.
I tested some alternatives available in some tutorials and the best for me was to use only classes in css.
One of the problems I encountered when using
require: did not override on some occasions
import: delay generated to load css
The best way for me was to actually put a class switch
.default-sidebar {
--side-text-icon:rgba(255,255,255,.9) !important;
--side-text-section: rgb(255,255,255,.8) !important;
--side-separator-section:#ff944d !important;
}
.dark-sidebar {
--side-text-icon:rgba(255,255,255,.9) !important;
--side-text-section: rgb(255,255,255,.8) !important;
--side-separator-section:#262626 !important;
}
'
<div className={`root-sidebar ${condition?'default-sidebar':'dark-sidebar'}`}></div>

Resources