How to group clases in CSS - Tailwind - html

I am trying to group clases so the code will be cleaner and legible. In the documentation of Tailwind it talks about "#apply", that can be used for this objective but I am using the CDN and therefore this is not working for me. So my question is, ¿Is there any form I can accomplish what I am looking for? Maybe by using SASS/SCSS or LESS?
Here is an example of what I wnat:
<ul class="md:flex md:items-center z-[-1] md:z-auto md:static absolute bg-gray-800 w-full left-0 md:w-auto md:py-0 py-4 md:pr-0 pr-7 md:pl-0 pl-7 md:opacity-100 opacity-0 top-[-400px] transition-all ease-in duration-200">
<li class="nav-element">
Home
</li>
<li class="px-4 py-6 md:py-0 hover:bg-yellow-500 md:hover:bg-transparent text-white duration-500">
About Us
</li>
<li class="px-4 py-6 md:py-0 hover:bg-yellow-500 md:hover:bg-transparent text-white duration-500">
Services
</li>
<li class="px-4 py-6 md:py-0 hover:bg-yellow-500 md:hover:bg-transparent text-white duration-500">
Contact Us
</li>
<button class="md:w-auto w-full bg-transparent text-white font-[Poppins] duration-500 px-6 py-2 hover:bg-white hover:text-gray-800 border border-white border-dotted rounded-lg">
Log In
</button>
<button class="md:w-auto w-full bg-yellow-500 text-white font-[Poppins] duration-500 px-6 py-2 md:mx-4 hover:bg-yellow-600 rounded-lg">
Sign In
</button>
</ul>
<ul class="nav-elemnts">
<li class="nav-element">
Home
</li>
<li class="nav-element">
About Us
</li>
<li class="nav-element">
Services
</li>
<li class="nav-element">
Contact Us
</li>
<button class="button-login">
Log In
</button>
<button class="button-signin">
Sign In
</button>
</ul>

Tailwind encourages you to use components. Instead of copy pasting the classes all over the place, you should use a system that allows you to create and use components.
Since your question is HTML + CSS only, you don't really have the right tools for this. But if you were using a scripting language like JS, Python, PHP etc., you could create components from elements and reuse them. Since I am familiar with React framework, I can show an example of that:
function NavElement(props) {
return (
<li class="px-4 py-6 md:py-0 hover:bg-yellow-500 md:hover:bg-transparent text-white duration-500">
{props.children}
</li>
)
}
and then use it as
function NavElements() {
return (
<ul class="md:flex md:items-center z-[-1] md:z-auto md:static absolute bg-gray-800 w-full left-0 md:w-auto md:py-0 py-4 md:pr-0 pr-7 md:pl-0 pl-7 md:opacity-100 opacity-0 top-[-400px] transition-all ease-in duration-200">
<NavElement href="/">Home</NavElement>
<NavElement href="/services">Services</NavElement>
<NavElement href="/about-us">About us</NavElement>
</ul>
)
}
As you can see, with this approach, you extract the huge list of modifiers into a small component that you can use multiple times without much repetition in the code.
You are free to choose any tool, language, system that will enable making components. That is what Tailwind kind of expects you to do.

I know you asked for SASS/LESS type approaches, but I think that adds additional complexity that you may not need. I think some simple JS would be a good candidate solution.
The way I've done it is to sort of replace CSS or StyledComponents with a JSON object of class names and a utility function to turn these into one big string.
First, the utility. Put this somewhere shared:
// turns a JSON object's values into a single string (keys are irrelevant)
export const classify = (classes) => Object.values(classes).join(' ')
Then in a file like styles.js that's next to the index.js, I'd have:
import { classify } from 'shared/utils'
export const nav = classify({
base: 'absolute bg-gray-800 w-full left-0 pr-7 pl-7 py-4 opacity-0 top-[-400px] z-[-1]',
animation: 'transition-all ease-in duration-200',
larger: 'md:flex md:items-center md:z-auto md:static md:w-auto md:py-0 md:pr-0 md:pl-0 md:opacity-100'
})
export const navItem = classify({
base: 'px-4 py-6 hover:bg-yellow-500 text-white',
resp: 'md:py-0 md:hover:bg-transparent',
anim: 'duration-500'
})
This is just like your CSS file. Yes, you have to name the variables, but you'd also have to name your components doing it the other way and this keeps things way less littered with files. You also get the benefit of seeing your HTML structure rather than a bunch of component names (which are more than likely just divs, smh). Also, you can have any number of key/values in your JSON, however you see fit to organize for reduced cognitive load while maintaining.
Then, the HTML/JSX:
import * as styles from './styles'
export default Component = (props) => (
<ul className={styles.nav}>
<li className={styles.navItem}> ... </li>
<li className={styles.navItem}> ... </li>
<li className={styles.navItem}> ... </li>
</ul>
)
The thing I like about this approach is that it's very similar to the CSS workflow and the JSON objects allow you to organize your class names in any way you want (ideally not doing it nested or you'll need a more complex classify()). Using components, you still have big long strings to mess with which is annoying when you have tens of classes applied. You can even do these in the same file if you'd like, it's just JavaScript.
You can even create utility styles/classes this way and concat them with template strings:
import * as utilStyles from 'utils/styles'
import * as styles from './styles'
<section className={`${utilStyles.shadowPanel} ${styles.mainSection}`>
...
</section>

have you tried doing:
<style type="text/tailwindcss">
#layer components {
.some-class {
#apply px-4 py-6 md:py-0 hover:bg-yellow-500 md:hover:bg-transparent text-white duration-500;
}
}
</style>

You should add 'group' class to parent and after work with group subclasses:
<div class="group p-4">
<p class="group-hover:bg-red-400">lorem ipsum</p>
</div>
after this code if you hover on div element, p elements backgroundColor will be change to red.

Related

TailwindCSS - is there a way to not write multiple times the same prefix? like `hover:` for example

The problem:
class="hover:bg-blue-400 hover:-translate-y-2 hover:-translate-x-2 hover:scale-110 hover:shadow-2xl hover:shadow-blue-400 hover:text-white"
here you see, there is the same prefix repetition.
hover:foo hover:bar hover:hello hover:world hover:something hover:another
I want to know if is there a way to not write multiple times the hover: prefix?
The idea:
is do something like:
hover:(class class class class class)
with brackets or something like that, so all the classes inside the () will be like one class and automatically added to the hover:
I think this idea there is in tailwind but I don't know the syntax for that.
if is possible this solution needs to work also with all the other prefixes
simple example demo:
// not important, only for deleting the console.warn()
console.clear();
<script src="https://cdn.tailwindcss.com"></script>
<body class="flex h-screen">
<button class="m-auto p-4 rounded-md bg-blue-200 transition hover:bg-blue-400 hover:-translate-y-2 hover:-translate-x-2 hover:scale-110 hover:shadow-2xl hover:shadow-blue-400 hover:text-white">
hello world
</button>
</body>
I saw all the docs, that is not talking about this concept: https://tailwindcss.com/docs/hover-focus-and-other-states#hover-focus-and-active
if there is someone experienced in this thing, it will be helpful!
like he said #diego in the comment, this is technically not possible with tailwind only.
tailwind framework alternative
maybe you use a tailwind framework like windiCSS https://windicss.org/features/variant-groups.html
that have this functionality:
<div class="hover:(bg-gray-400 font-medium) bg-white font-light"/>
javascript vanilla (simple script)
want tailwind only?
so I think that maybe we can create a simple JS script to solve this problem.
twHover();
function twHover() {
// get only the elements that have the hover attribute
let hoverEls = document.querySelectorAll("[data-hover]");
// loop through the elements that have the hover attribute
hoverEls.forEach((el) => {
// we get the string inside the attribute
// and then make it into a array
let twHoverClasses = `${el.dataset.hover}`.split(" ");
// loop through the classes inside the element's attributes
twHoverClasses.forEach((className) => {
// add the class for you `hover:className`
el.classList.add(`hover:${className}`);
});
});
}
<script src="https://cdn.tailwindcss.com"></script>
<body class="flex h-screen">
<!-- original -->
<button class="m-auto p-4 rounded-md bg-blue-200 transition hover:bg-blue-400 hover:-translate-y-2 hover:-translate-x-2 hover:scale-110 hover:shadow-2xl hover:shadow-blue-40 hover:text-white">original</button>
<!-- with script -->
<button data-hover="bg-blue-400 -translate-y-2 -translate-x-2 scale-110 shadow-2xl shadow-blue-40 text-white" class="m-auto p-4 rounded-md bg-blue-200 transition">with script</button>
</body>
want more from the JS script?
also :focus, :lg, :sm, and so on.
use this:
// this can be any preudo class that tailwind can have
twPseudo("focus");
// if there is nothing as parameter, we use hover
twPseudo();
function twPseudo(pseudo = "hover") {
// get only the elements that have the hover attribute
let hoverEls = document.querySelectorAll(`[data-${pseudo}]`);
// loop through the elements that have the hover attribute
hoverEls.forEach((el) => {
// we get the string inside the attribute
// and then make it into a array
let twHoverClasses = `${el.dataset[pseudo]}`.split(" ");
// loop through the classes inside the element's attributes
twHoverClasses.forEach((className) => {
// add the class for you `hover:className`
el.classList.add(`${pseudo}:${className}`);
});
});
}
<script src="https://cdn.tailwindcss.com"></script>
<body class="grid grid-cols-2 place-items-center h-screen">
<!-- original -->
<div>
<h2 class="text-3xl font-bold text-blue-500 mb-4">original</h2>
<!-- hover -->
<button class="m-auto p-4 rounded-md bg-blue-200 transition hover:bg-blue-400 hover:-translate-y-2 hover:-translate-x-2 hover:scale-110 hover:shadow-2xl hover:shadow-blue-40 hover:text-white">hover</button>
<!-- focus -->
<button class="m-auto p-4 rounded-md bg-blue-200 transition focus:bg-blue-400 focus:-translate-y-2 focus:-translate-x-2 focus:scale-110 focus:shadow-2xl focus:shadow-blue-40 focus:text-white">focus</button>
</div>
<!-- with script -->
<div>
<h2 class="text-3xl font-bold text-blue-500 mb-4">with script</h2>
<!-- hover -->
<button data-hover="bg-blue-400 -translate-y-2 -translate-x-2 scale-110 shadow-2xl shadow-blue-40 text-white" class="m-auto p-4 rounded-md bg-blue-200 transition">hover</button>
<!-- focus -->
<button data-focus="bg-blue-400 -translate-y-2 -translate-x-2 scale-110 shadow-2xl shadow-blue-40 text-white" class="m-auto p-4 rounded-md bg-blue-200 transition">focus</button>
</div>
</body>
also make sure to put the script code at the end of the page or inside a DomContentLoaded event
advantages:
less repetitive characters to type
more than 25 chars saved (only in your example)
multiple line attribute
As you can see you can write your classes in one line,
and the hover logic in another line. making it easy to debug.
works out of the box.
just copy and paste, and call the function.
with the correct parameter (focus, sm, lg, xl, 2xl) or without any parameter (will be hover)
// just call it at the end of the page
twPseudo();
You can just create a new class in a <style> block in your page or template. And then use #apply to use the needed tailwind classes. Like:
<style>
.mybutton {
#apply m-auto p-4 rounded-md bg-blue-200 transition
}
.mybutton:hover {
#apply bg-blue-400 -translate-y-2 -translate-x-2 scale-110 shadow-2xl shadow-blue-400 text-white
}
</style>
Now, if you set the mybutton class on the button, the hover will also work.
You can also add these classes to the main css file of your project. This is not the preferred way of tailwind, though. See Tailwind documentation.
For people using react who stumble upon the question, there is a better approach for the following:
const pseudoJoin = (selector, str) => {
return selector+":"+str.split(" ").join(" "+selector+":")
}
Now you can call it anywhere like:
<div className=`${pseudoJoin('hover','classes you want on hover')} some more classes`>Hello World!</div>
Or when you are using classnames framework:
<div className={ classnames(
pseudoJoin('hover', 'classes you want on hover'),
"Other classes here"
)}>Hello World!</div>

Buttons stop working when I add new div element

I am using sveltekit, typscript, and tailwind for this. Ok, I have a website that I am making and I have buttons that have no background and are formated like this:
When opened
- Label
stuff
when closed
+ Label
It worked and all but when I added a new div to be right next to those buttons the buttons stopped working completely. It would not even show the pointer hand.
Code for buttons:
<script lang="ts">
let expanded: boolean = true;
export let item: string = '';
export let value: string[] = [];
import Page from '../Components/page.svelte';
let pagen: string = 'About';
</script>
<Page bind:page={pagen} run="" />
<div class="dropdown flex flex-col mt-5">
<button
class="dropdown-toggle"
on:click={() => {
expanded = !expanded;
}}
>
<span class="dropdown-label text-gray-400 ml-5 flex text-lg"
>{expanded ? `- ${item}` : `+ ${item}`}</span
>
</button>
<div class="dropdown-content flex" style:display={expanded ? 'block' : 'none'}>
{#each value as val}
<div class="dropdown-item text-gray-400 ml-10">
<p class="inline-block text-gray-400">#</p>
<button
on:click={() => {
pagen = `{val}`;
}}>{val}</button
>
</div>
{/each}
</div>
</div>
The pagen that is binded is for the div that goes right next to it. (this is not important I think...)
Code for the page:
<script lang="ts">
export let page: string = 'About';
export let run: string = '';
</script>
{#if run == 'true'}
<div class="w-screen h-screen -z-10">
<div class="page ml-80 bg-gray-800 h-screen">
{#if page == 'About'}
<div class="flex flex-col items-center justify-center">
<h1 class="text-gray-400 text-3xl font-bold tracking-wider mt-
10">Some Title</h1>
<p class="text-gray-400 text-2xl font-bold tracking-wider">Some
Label</p>
</div>
{/if}
</div>
</div>
{/if}
These are both components in svelte and are imported in index.svelte. Buttons on top and page on bottom.
Helpful Images:
The website is being styled to look like discord.
Any help is greatly appreciated!
There are a few things I'm noticing that may help you get this working:
Nothing may be showing because values may be empty. When I added values = [ "a", "b", "c" ] I saw them no problem (see repl).
You're not setting run, so Page will never render. Also, using run as a string is weird since it appears it is a boolean?
If you're using Tailwind, instead use conditional classes:
<div class="dropdown-content flex" style:display={expanded ? 'block' : 'none'}>
<!-- should be: -->
<div class="dropdown-content flex" style:block={expanded} style:hidden={!expanded}>
Stylistic recommendation:
<span class="dropdown-label text-gray-400 ml-5 flex text-lg">
{expanded ? `- ${item}` : `+ ${item}`}
</span>
<!-- simpler: -->
<span class="dropdown-label text-gray-400 ml-5 flex text-lg">
{expanded ? "-" : "+"} {item}
</span>
Here is a repl with what I believe is working code.

Unable to apply flex no wrap

Hello I need a small help I want to hide button background and I am unable to apply flex which wraps the items contained in a container in which it is applied into single line
My html code:
import {
GlobeIcon,
HomeIcon
}from '#heroicons/react/outline'
function Sidebar(){
return (
<div>
<button className='flex items-center space-x-2 hover:text-white'>
<HomeIcon className = 'h-5 w- 5'/>
<p1>Home</p1>
</button>
<button className='flex items-center space-x-2 hover:text-white'>
<GlobeIcon className = 'h-5 w- 5'/>
<p1>Explore</p1>
</button>
</div>
)
}
export default Sidebar;
I want my page to look like
But my actual page is shwing like
Anybody could help me with it

How to do line-clamp with a learn more button in vue3

I am using vue3 with vitejs in one of my projects and I have stumbled upon an issue. I have the following codes.
<div>
<p class="line-clamp-4">Some really long text here...</p>
<a id="read-more" href="readmore.html">Read more</a>
</div>
I am using tailwindcss here and I was able to clamp 4 lines. I have a list of these texts and not every one of them will have line clamp. So, I only want to show the read more button, when the line-clamp is applied. Now, how do I know the line-clamp is applying to this text and I have to show the readmore button?
Here is what I did to achieve this:
#foreach($module->captions as $caption)
<div class="mt-1 relative">
<div id="body-{{$caption->id}}" class="text-md leading-7 text-gray-700 font-normal whitespace-pre-line line-clamp-6">
{{ $caption->body }}
</div>
</div>
<div id="readMore-{{$caption->id}}" class="hidden flex justify-center absolute bottom-0 left-0 bg-white w-full rounded-b-lg">
<p class="text-blue-600 cursor-pointer py-2"></p>
</div>
#endforeach
<script>
let elements = document.getElementsByClassName('line-clamp-6')
Array.from(elements).forEach((element) => {
let captionId = element.id.split('-')[1];
let body = document.getElementById('body-' + captionId)
let readMore = document.getElementById('readMore-' + captionId)
if (element.clientHeight === 168) {
readMore.firstElementChild.innerText = 'Read More'
readMore.classList.remove('hidden')
readMore.addEventListener('click', (el) => {
if (body.classList.contains('line-clamp-6')) {
body.classList.remove('line-clamp-6')
readMore.firstElementChild.innerText = 'Read Less'
} else {
body.classList.add('line-clamp-6')
readMore.firstElementChild.innerText = 'Read More'
}
})
}
})
</script>
In my case I'm using a foreach to loop through some data but you should still be able to customize this to your needs. Basically, I'm getting all the elements that have a class name of line-clamp-6. Then were looping through those elements to see if they have a clientHeight of 168. If so, they need the Read More link.
Keep in mind you might need to check for a different clientHeight.
Then I listen for a click on the read more link if line-clamp-6 is present then we remove it else we add it.
Hopefully this helps you. I through this together in the last hour so there may be a better way to do it but this seems to work for me.

Implementing Custom dropdown in Angular 2/4

I have been working on a custom dropdown functionality with a horizontal divider seperating two sets of values and have general select dropdown functionalities like using up and down arrow to navigate through the values, as well as go to specific values on press of a alphabet.
Code:
<div class="btn-group d-flex dropdown" dropdown>
<div class="floatLabelContainer w-100">
<button id="button-basic" dropdownToggle type="button" float-label [addLabel]="false" [hasFloat]="true" class="mb-2" aria-controls="dropdown-basic">
{{selectedCountry}}
<span class="caret" id="country-caret"></span>
</button>
<label for="button-basic" class="label-class" id="label-class">Country</label>
</div>
<ul id="dropdown-basic" slimScroll width="100%" height="250px" size="3px" alwaysVisible="true" wheelStep="20" *dropdownMenu
class="dropdown-menu d-block" role="menu" aria-labelledby="button-basic">
<li role="menuitem">
<a class="dropdown-item" tabindex="0" (click)="selectedCountry = country.name" *ngFor="let country of restOfCountries | orderBy : 'name'">{{country.name}}</a>
</li>
<li class="divider dropdown-divider"></li>
<li role="menuitem">
<a class="dropdown-item" tabindex="0" (click)="selectedCountry = country.name" *ngFor="let country of asianCountries | orderBy : 'name'">{{country.name}}</a>
</li>
</ul>
</div>
I have been able to achieve the divider part with the above code,
but is there away to implement arrows and alphabet press functionality also.
I would prefer to have angular specific implementation or a plugin, rather than js or jQuery
There is the possibility of evaluating keydown and keyup events:
https://alligator.io/angular/binding-keyup-keydown-events/
According to this you should be able to achieve this functionality by implementing click listeners such as:
<div class="btn-group d-flex dropdown"
(keydown.arrowup)="select(items[i-1])"
(keydown.arrowdown)="select(items[i+1])"
dropdown>
EDIT: so I tried this and turns out this does not work on any item on default. It works on inputs, how the example shows but I could not get i ti to work on a list by now.