Types for tailwind - reactjs

I am using tailwind.config.js in order to extend some colors:
const theme = {
mode: 'jit',
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
darkMode: 'media', // 'media' or 'class'
theme: {
extend: {
colors: {}
but then I might pass a color to a component in props in order to later use it as border-${color} and/or text-${color}.
Right now the value would be just of type string so I am not getting the tips, is there some way to get a type of the combined custom colors + default tailwind's colors ?

You have to return the complete string border-red-500 in place of border-${color}.
Like you can define a function like this
let {color} = props;
function getColor( color) {
return 'border-" + color;
}
And then use it like this
<div className={`border-2 ${getColor}`}> hello world </div>

Related

Tailwind ignoring darkMode: "class", not paying attention to class="dark" on html tag

Using tailwind and react, I am using a config file as follows:
/** #type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{html,js,jsx,ts,tsx}"],
darkMode: "class",
important: "#app",
theme: {
colors: {
...
},
fontFamily: {
...
},
extend: {},
},
plugins: [],
corePlugins: {
preflight: false,
},
};
and setting the class as follows
darkMode
? document.documentElement.classList.add("dark")
: document.documentElement.classList.remove("dark");
Inspecting the top element, it is indeed adding and removing a class="dark" to the top element. However, two elements like
text-grey-300 dark:text-grey-700
aren't changing when the class changes, at all. When the darkMode: Class is removed, it accurately follows OS preference however. Whats going on?
I have tried changing the class selector manually, tried placing the dark on other elements, moving the declaration around in my config file, none of it seems to help.

Tailwind CSS, certain custom colors are not working

I'm trying to use Tailwind custom colors in my project by writing some themes in tailwind.config.js extend.
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
theme: {
extend: {
colors: {
s2condPurple: '#a32eff', // works ⭕️
s2condPink: '#ff0099', // works ⭕️
s2condOrange: '#ff5f55', // works ⭕️
s2condYellow: '#ffe600', // doesn't work ❌
s2condLime: '#cdff64', // works ⭕️
s2condMint: '#2af1b5', // works at 'text-s2condMint' but not at 'border-s2condMint'
secondTest: '#ffe600', // works ⭕️ <-- I tested it for s2condYellow but it works perfectly!
s2condTest2: '#2af1b5', // doesn't work ❌
...
},
},
},
plugins: [],
}
I'm using these colors in my code like this:
const colorList: colorListType = {
life: 'white',
identity: 's2condPurple',
arts: 's2condPink',
industry: 's2condOrange',
knowledge: 'secondTest',
sports: 's2condLime',
languages: 'secondTest',
}
const { [data.name.en.toLowerCase()]: color } = colorList
...
<button
className={`border focus:outline-none hover:border-${color} active:border-${color} ${
clicked ? `border-${color}` : 'border-textBlack'
} `}
>
<p className="text-white">{value.kr}</p>
</button>
Can I get a clue about this issue??
Newer versions of Tailwind only seem to add classes that have been used in your code. When using dynamic classes (like the ones in your example) you will have to declare them within the safelist property.
Here's an example of one way your could do this:
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
theme: {
extend: {
colors: {
s2condPurple: '#a32eff', // works ⭕️
s2condPink: '#ff0099', // works ⭕️
s2condOrange: '#ff5f55', // works ⭕️
s2condYellow: '#ffe600', // should work⭕️
s2condLime: '#cdff64', // works ⭕️
s2condMint: '#2af1b5', // works at 'text-s2condMint' but not at 'border-s2condMint'
secondTest: '#ffe600', // works ⭕️ <-- I tested it for s2condYellow but it works perfectly!
s2condTest2: '#2af1b5', // should work ⭕️
},
},
},
plugins: [],
safelist: [{
pattern: /(bg|text|border)-s2cond(Purple|Pink|Orange|Yellow|Lime|Mint|Test|Test2)/
}
]
}
You can read more about this in the documentation https://tailwindcss.com/docs/content-configuration#safelisting-classes.
Update: 8th June 2022
If you work with a lot of dynamic margins or dimensions, you might want to add the following to your safelist property.
{
pattern: /(mt|mb|mr|ml|my|mx|px|py|pt|pb|pl|pr)-[0-9]+/
},
{
pattern: /flex-.*/
},
{
pattern: /(bottom|right|top|left)-[0-9]+/
},
{
pattern: /(w|h)-[0-9]+/
}
Hope this saves someone else's time.
https://tailwindcss.com/docs/content-configuration#dynamic-class-names
Here's the answer.
<div class="text-{{ error ? 'red' : 'green' }}-600"></div>. // ❌
<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>. // ⭕️
I did it in a wrong way :(

Dynamically build classnames in TailwindCss

I am currently building a component library for my next project with TailwindCss, I just ran into a small issue when working on the Button component.
I'm passing in a prop like 'primary' or 'secondary' that matches a color I've specified in the tailwind.config.js then I want to assign that to the button component using Template literals like so: bg-${color}-500
<button
className={`
w-40 rounded-lg p-3 m-2 font-bold transition-all duration-100 border-2 active:scale-[0.98]
bg-${color}-500 `}
onClick={onClick}
type="button"
tabIndex={0}
>
{children}
</button>
The class name comes through in the browser just fine, it shows bg-primary-500 in the DOM, but not in the applied styles tab.
The theming is configured like so:
theme: {
extend: {
colors: {
primary: {
500: '#B76B3F',
},
secondary: {
500: '#344055',
},
},
},
},
But it doesn't apply any styling. if I just add bg-primary-500 manually it works fine.
I'm honestly just wondering if this is because of the JIT compiler not picking dynamic classnames up or if I'm doing something wrong (or this is just NOT the way to work with tailWind).
Any help is welcome, thanks in advance!
So after finding out that this way of working is not recommended and that JIT doesn't support it (Thanks to the generous commenters). I have changed the approach to a more 'config' based approach.
Basically I define a const with the basic configuration for the different props and apply those to the component. It's a bit more maintenance work but it does the job.
Here is the example of a config. (Currently without typing) and up for some better refactoring but you'll get the idea.
const buttonConfig = {
// Colors
primary: {
bgColor: 'bg-primary-500',
color: 'text-white',
outline:
'border-primary-500 text-primary-500 bg-opacity-0 hover:bg-opacity-10',
},
secondary: {
bgColor: 'bg-secondary-500',
color: 'text-white',
outline:
'border-secondary-500 text-secondary-500 bg-opacity-0 hover:bg-opacity-10',
},
// Sizes
small: 'px-3 py-2',
medium: 'px-4 py-2',
large: 'px-5 py-2',
};
Then I just apply the styling like so:
<motion.button
whileTap={{ scale: 0.98 }}
className={`
rounded-lg font-bold transition-all duration-100 border-2 focus:outline-none
${buttonConfig[size]}
${outlined && buttonConfig[color].outline}
${buttonConfig[color].bgColor} ${buttonConfig[color].color}`}
onClick={onClick}
type="button"
tabIndex={0}
>
{children}
</motion.button>
this way of writing Tailwind CSS classes is not recommended. Even JIT mode doesn't support it, to quote Tailwind CSS docs: "Tailwind doesn’t include any sort of client-side runtime, so class names need to be statically extractable at build-time, and can’t depend on any sort of arbitrary dynamic values that change on the client"
EDIT: Better implementation 2022 - https://stackoverflow.com/a/73057959/11614995
Tailwind CSS does not support dynamic class names (see here). However, there's still a way to accomplish this. I needed to use dynamically build class names in my Vue3 application. See the code example below.
Upon build tailwind scanes your application for classes that are in use and automatically purges all other classes (see here). There is however a savelist feature that you can use to exclude classes from purging - aka they will always make it to production.
I have created a sample code below, that I use in my production. It combines each color and each color shade (colorValues array).
This array of class names is passed into the safelist. Please note, that by implementing this feature you ship more css data to production as well as ship css classes you may never use.
const colors = require('./node_modules/tailwindcss/colors');
const colorSaveList = [];
const extendedColors = {};
const colorValues = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
for (const key in colors) {
// To avoid tailWind "Color deprecated" warning
if (!['lightBlue', 'warmGray', 'trueGray', 'coolGray', 'blueGray'].includes(key))
{
extendedColors[key] = colors[key];
for(const colorValue in colorValues) {
colorSaveList.push(`text-${key}-${colorValue}`);
colorSaveList.push(`bg-${key}-${colorValue}`);
}
}
}
module.exports = {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}"
],
safelist: colorSaveList,
theme: {
extend: {
colors: extendedColors
}
},
plugins: [
require('tailwind-scrollbar'),
]
}
For tailwind JIT mode or v3 that uses JIT, you have to ensure that the file where you export the object styles is included in the content option in tailwind.config.js, e.g.
content: ["./src/styles/**/*.{html,js}"],
If someone comes across in 2022 - I took A. Mrózek's answer and made a couple of tweaks to avoid deprecated warnings and an issue with iterating non-object pallettes.
const tailwindColors = require("./node_modules/tailwindcss/colors")
const colorSafeList = []
// Skip these to avoid a load of deprecated warnings when tailwind starts up
const deprecated = ["lightBlue", "warmGray", "trueGray", "coolGray", "blueGray"]
for (const colorName in tailwindColors) {
if (deprecated.includes(colorName)) {
continue
}
const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]
const pallette = tailwindColors[colorName]
if (typeof pallette === "object") {
shades.forEach((shade) => {
if (shade in pallette) {
colorSafeList.push(`text-${colorName}-${shade}`)
colorSafeList.push(`bg-${colorName}-${shade}`)
}
})
}
}
// tailwind.config.js
module.exports = {
safelist: colorSafeList,
content: ["{pages,app}/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
colors: tailwindColors,
},
},
plugins: [],
}
this might be a bit late, but for the people bumping this thread.
the simplest explaination for this is;
Dynamic Class Name does not work unless you configured Safelisting for the Dynamic class name,
BUT, Dynamic Class works fine so long as its a full tailwind class name.
its stated here
this will not work
<div class="text-{{ error ? 'red' : 'green' }}-600"></div>
but this one works
<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>
its states;
As long as you always use complete class names in your code, Tailwind
will generate all of your CSS perfectly every time.
the longer explanation;
Tailwind will scan all the files specified in module.exports.content inside tailwind.config.js and look for tailwind classes, it does not even have to be in a class attribute and can even be added in commented lines, so long as the full class name is present in that file and class name is not dynamically constructed; Tailwind will pull the styling for that class,
so in your case, all you have to do is put in the full class name inside that file for all the possible values of your dynamic class
something like this
<button className={ color === 'primary' ? 'bg-primary-500' : 'bg-secondary-500'}>
{children}
</button>
or the method I would prefer
<!-- bg-primary-500 bg-secondary-500 -->
<button className={`bg-${color}-500 `}>
{children}
</button>
here's another example, although its Vue, the idea would be the same for any JS framework
<template>
<div :class="`bg-${color}-100 border-${color}-500 text-${color}-700 border-l-4 p-4`" role="alert">
test
</div>
</template>
<script>
/* all supported classes for color props
bg-red-100 border-red-500 text-red-700
bg-orange-100 border-orange-500 text-orange-700
bg-green-100 border-green-500 text-green-700
bg-blue-100 border-blue-500 text-blue-700
*/
export default {
name: 'Alert',
props: {
color: {type: String, default: 'red'}
}
}
</script>
and the result would be this
<Alert color="red"></Alert> <!-- this will have color related styling-->
<Alert color="orange"></Alert> <!-- this will have color related styling-->
<Alert color="green"></Alert> <!-- this will have color related styling-->
<Alert color="blue"></Alert> <!-- this will have color related styling-->
<Alert color="purple"></Alert> <!-- this will NOT have color related styling as the generated classes are not pre-specified inside the file -->
Now could use safeListing
and tailwind-safelist-generator package to "pregenerate" our dynamics styles.
With tailwind-safelist-generator, you can generate a safelist.txt file for your theme based on a set of patterns.
Tailwind's JIT mode scans your codebase for class names, and generates CSS based on what it finds. If a class name is not listed explicitly, like text-${error ? 'red' : 'green'}-500, Tailwind won't discover it. To ensure these utilities are generated, you can maintain a file that lists them explicitly, like a safelist.txt file in the root of your project.
In v3 as Blessing said you can change the content array to support that.
I had this
const PokemonTypeMap = {
ghost: {
classes: "bg-purple-900 text-white",
text: "fantasma",
},
normal: {
classes: "bg-gray-500 text-white",
text: "normal",
},
dark: {
classes: "bg-black text-white",
text: "siniestro",
},
psychic: {
classes: "bg-[#fc46aa] text-white",
text: "psíquico",
},
};
function PokemonType(props) {
const pokemonType = PokemonTypeMap[props.type];
return (
<span
className={pokemonType.classes + " p-1 px-3 rounded-3xl leading-6 lowercase text-sm font-['Open_Sans'] italic"}
>
{pokemonType.text}
</span>
);
}
export default PokemonType;
something similar to your approach, then I moved the array to a JSON file, it thought was working fine, but was browser caché... so following Blessing's response, you can add .json like this
content: ["./src/**/*.{js,jsx,ts,tsx,json}"],
Finally I have this code, it's better in my view.
import PokemonTypeMap from "./pokemonTypeMap.json";
function PokemonType(props) {
const pokemonType = PokemonTypeMap[props.type];
return (
<span className={pokemonType.classes + " p-1 px-3 rounded-3xl leading-6 lowercase text-sm font-['Open_Sans']"}>
{pokemonType.text}
</span>
);
}
export default PokemonType;
Is it recommended to use dynamic class in tailwind ?
No
Using dynamic classes in tailwind-css is usually not recommended because tailwind uses tree-shaking i.e any class that wasn't declared in your source files, won't be generated in the output file.
Hence it is always recommended to use full class names
According to Tailwind-css docs
If you use string interpolation or concatenate partial class names together, Tailwind will not find them and therefore will not generate the corresponding CSS
Isn't there work around ?
Yes
As a last resort, Tailwind offers Safelisting classes.
Safelisting is a last-resort, and should only be used in situations where it’s impossible to scan certain content for class names. These situations are rare, and you should almost never need this feature.
In your example,you want to have 100 500 700 shades of colors. You can use regular expressions to include all the colors you want using pattern and specify the shades accordingly .
Note: You can force Tailwind to create variants as well:
In tailwind.config.js
module.exports = {
content: [
'./pages/**/*.{html,js}',
'./components/**/*.{html,js}',
],
safelist: [
{
pattern: /bg-(red|green|blue|orange)-(100|500|700)/, // You can display all the colors that you need
variants: ['lg', 'hover', 'focus', 'lg:hover'], // Optional
},
],
// ...
}
EXTRA: How to automate to have all tailwind colors in the safelist
const tailwindColors = require("./node_modules/tailwindcss/colors")
const colorSafeList = []
// Skip these to avoid a load of deprecated warnings when tailwind starts up
const deprecated = ["lightBlue", "warmGray", "trueGray", "coolGray", "blueGray"]
for (const colorName in tailwindColors) {
if (deprecated.includes(colorName)) {
continue
}
// Define all of your desired shades
const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]
const pallette = tailwindColors[colorName]
if (typeof pallette === "object") {
shades.forEach((shade) => {
if (shade in pallette) {
// colorSafeList.push(`text-${colorName}-${shade}`) <-- You can add different colored text as well
colorSafeList.push(`bg-${colorName}-${shade}`)
}
})
}
}
// tailwind.config.js
module.exports = {
safelist: colorSafeList, // <-- add the safelist here
content: ["{pages,app}/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
colors: tailwindColors,
},
},
plugins: [],
}
Note: I have tried to summarize the answer in all possible ways, In the combination of all possible answers.Hope it helps
I had a similar issue, instead of passing all the possible configs, I just pass the data to the style property of the HTML. This is far more efficient!
or
Pass a class name as a prop and let the user of the package write the styles to that class.
const CustomComp = ({
keyColGap = 0,
keyRowGap = 0,
className = '',
}: Props) => {
const classNameToRender = (): string => {
return `m-1 flex flex-col ${className}`.trim();
};
const rowStylesToRender = (): React.CSSProperties | undefined => {
const styles: React.CSSProperties | undefined = { gap: `${keyRowGap}rem` };
return styles;
};
const colStylesToRender = (): React.CSSProperties | undefined => {
const styles: React.CSSProperties | undefined = { gap: `${keyColGap}rem` };
return styles;
};
return (
<div className={classNameToRender()} style={rowStylesToRender()}>
{layout.map((row) => {
return (
<div
className={`flex justify-around`}
style={colStylesToRender()}
key={row}
>
/* Some Code */
</div>
);
})}
</div>
}

Apply style conditionally on a React component with json-style definitions

I have a component where style is applied in form of json and I need to override styles conditionally.
See style definitions:
const classes = {
txt: {
color: 'green',
...
},
txtBlue:{
color: 'blue',
..
},
};
See template:
<div style={classes.txt + (this.state.goBlue ? classes.txtBlue)}></div>
The + (this.state.goBlue ? classes.txtBlue) I have written above is not working and it is just to show what I need to understand and make work.
When this.state.goBlue is true, I want both classes.txt and classes.txtBlue to apply to the div.
Thanks
You didn't use the ternary operator correctly, you can do something like this:
<div style={ this.state.goBlue ? { ...classes.txt, ...classes.txtBlue } : classes.txt }></div>
This will apply both styles if this.state.goBlue is truthy, otherwise it will only apply classes.txt.
Found the solution!
By adding this to the render() function:
const txtStyle =
this.state.goBlue ?
Object.assign({}, classes.txt, classes.txtBlue) :
Object.assign({}, classes.txt);
And this to the template:
<div style={txtStyle}></div>
I was able to achieve what I wanted.

vis.js network: Edge colors not displaying in react.js app

I am having a problem with the edge colors when rendering a network visualisation through a react app.
I have a MindMapComponent which contains a network.
I receive the data for the network through the props for the component:
class MindMapComponent extends React.Component {
//React component to display a single submission Item.
//Displays the text and author of a Perspective Item
constructor(props) {
super(props);
this.state = {map: props.mindMap, node_options: props.node_options, edge_options: props.edge_options}
}
I then go ahead and create the network in my componentDidMount function:
componentDidMount(){
var edge_dataset = new vis.DataSet(JSON.parse(this.state.map.conceptmap_edges));
var nodes_dataset = new vis.DataSet(JSON.parse(this.state.map.conceptmap_nodes));
var data = {
nodes:nodes_dataset ,
edges: edge_dataset
};
var edge_options =JSON.parse(this.state.edge_options);
edge_options.color = {inherit:false};
var options = {
edges: edge_options,
nodes: JSON.parse(this.state.node_options),
physics: false,
locale: 'en',
interaction: {
navigationButtons: true,
keyboard: true,
hover: true
}
};
var network = new vis.Network(this.refs.network, data,options)
console.log(network);
}
And finally render the whole thing in my render function
render() {
const liStyle = {
borderStyle: 'outset',
backgroundColor: 'lightgrey',
marginBottom: '10px',
listStyleType: 'none'
};
const metaStyle = {
paddingLeft: '15px'
}
const networkStyle = {
height:'250px',
borderRight:'dashed 2px'
}
var dateString = new Date(this.state.map.date_added);
dateString = dateString.getDate() + "/" + (dateString.getMonth() +1) + "/" + dateString.getFullYear();
return <li key={this.state.map.id} style={liStyle}>
<div className = 'row'>
<div className = 'col-lg-8' ref = "network" style = {networkStyle}></div>
<div className = 'col-lg-4' style = {metaStyle}><br/><p>Submitted on: {dateString}</p></div>
</div>
</li>
}
The final network should look like this (rendered in normal html+js app).
However in my react app the colors of the edges do not display
I took a look inside of the network data structure (through the console.log(network) at the end of component did mount).
The body.data.edges part of the structure contains the correct color value. However the body.edge.[id].options.color part is empty
I believe this is the source of my problem but am not sure whether my analysis is correct or how to go about fixing it.
I think I have a solution (I ran into the same problem as you, but then in Angular2).
Try setting the color as an Object (see http://visjs.org/docs/network/edges.html) and specify the inherit property to false. E.g.(using typescript)
const myEdge: Edge = {
id: 'myEdge',
from: 'NODE1',
to: 'NODE2',
arrows: 'to',
color: {color: '#ff0000', inherit: false};
dashes: true
}
You might also want to set the highlight and hover colors

Resources