Useful CSS Stuff

I thought it might be good to have a topic with useful CSS that people have found and used over their journey to assist others or give ideas about how to achieve things via pure CSS rather than alternative methods.

If you choose to add to this topic, please include a screenshot demonstrating the effect!

In this spirit, here are some that i've come across (more to be added when I get time):

  • Add a "NEW" indicator to components
    image
/*
NewIndicator:
Adds a "NEW" box to the top-right of a component to indicate that the component was recently added.
Add a Perspective Style "NewIndicator" to make it selectable
*/
.psc-NewIndicator {
	position: relative; /* this sets the positioning system for the ::after component, otherwise the NEW will be positioned relative to the view, not the component */
	/*box-shadow: 0px 0px 0px 2px #C2DD53;*/ /* add a border around the component itself */
}
.psc-NewIndicator::after {
	content: 'NEW';
	position: absolute;
	top: 2px;
	right: 2px;
	width: 30px;
	height: 16px;
	padding: 2px;
	border-radius: 3px;
	background-color: #0A65EC;
	color: #fff;
	font-size: 0.75rem;
}

**I still haven't mastered em/rem units... perhaps these could replace these fixed px units. I couldn't get the padding to apply properly to give equal space around the text and background edges for all sides

  • gap for all flex containers (flex views, flex containers, and flex repeaters) to add spacing around components within a flex container. This works for wrapped components as well.
    image
23 Likes

Using CSS Custom Properties to Build Composable Style Systems


I'm gonna try to distill this down into something approachable.

Let's say you want to style your buttons. You make a palette of colors, and use those colors to style your button:

20240901_224432

[data-component='ia.input.button'] {
    color: var(--palette-primary-50);
    background-color: var(--palette-primary-500);
    border-color: var(--palette-primary-600);
    border-width: 1px;
    border-style: solid;
    border-radius: 5px;
}

:root {
    --palette-primary-50: #edf5fd;
    --palette-primary-100: #e3effb;
    --palette-primary-200: #c7dff7;
    --palette-primary-300: #97c3f0;
    --palette-primary-400: #4393e4;
    --palette-primary-500: #0b6bcb;
    --palette-primary-600: #185ea5;
    --palette-primary-700: #12467b;
    --palette-primary-800: #0a2744;
    --palette-primary-900: #051423;

    --palette-neutral-50: #fbfcfe;
    --palette-neutral-100: #f0f4f8;
    --palette-neutral-200: #dde7ee;
    --palette-neutral-300: #cdd7e1;
    --palette-neutral-400: #9fa6ad;
    --palette-neutral-500: #636b74;
    --palette-neutral-600: #555e68;
    --palette-neutral-700: #32383e;
    --palette-neutral-800: #171a1c;
    --palette-neutral-900: #0b0d0e;

    --palette-danger-50: #fef6f6;
    --palette-danger-100: #fce4e4;
    --palette-danger-200: #f7c5c5;
    --palette-danger-300: #f09898;
    --palette-danger-400: #e47474;
    --palette-danger-500: #c41c1c;
    --palette-danger-600: #a51818;
    --palette-danger-700: #7d1212;
    --palette-danger-800: #430a0a;
    --palette-danger-900: #240505;
}

Cool! But now all our buttons are blue, and we need more options. We have 3 color palettes, and it would be nice if we could use them all on our buttons.

Let's add a layer of indirection:

.psc-Color\/primary {
    --palette-50: var(--palette-primary-50);
    --palette-100: var(--palette-primary-100);
    --palette-200: var(--palette-primary-200);
    --palette-300: var(--palette-primary-300);
    --palette-400: var(--palette-primary-400);
    --palette-500: var(--palette-primary-500);
    --palette-600: var(--palette-primary-600);
    --palette-700: var(--palette-primary-700);
    --palette-800: var(--palette-primary-800);
    --palette-900: var(--palette-primary-900);
}

.psc-Color\/neutral {
    --palette-50: var(--palette-neutral-50);
    --palette-100: var(--palette-neutral-100);
    --palette-200: var(--palette-neutral-200);
    --palette-300: var(--palette-neutral-300);
    --palette-400: var(--palette-neutral-400);
    --palette-500: var(--palette-neutral-500);
    --palette-600: var(--palette-neutral-600);
    --palette-700: var(--palette-neutral-700);
    --palette-800: var(--palette-neutral-800);
    --palette-900: var(--palette-neutral-900);
}

.psc-Color\/danger {
    --palette-50: var(--palette-danger-50);
    --palette-100: var(--palette-danger-100);
    --palette-200: var(--palette-danger-200);
    --palette-300: var(--palette-danger-300);
    --palette-400: var(--palette-danger-400);
    --palette-500: var(--palette-danger-500);
    --palette-600: var(--palette-danger-600);
    --palette-700: var(--palette-danger-700);
    --palette-800: var(--palette-danger-800);
    --palette-900: var(--palette-danger-900);
}

And use this computed/indirect palette on our button:

[data-component='ia.input.button'] {
    color: var(--palette-50);
    background-color: var(--palette-500);
    border-color: var(--palette-600);
    border-width: 1px;
    border-style: solid;
    border-radius: 5px;
}

Now, by applying the style classes Color\neutral, Color\primary, and Color\danger we can get three different looks for our button.
20240901_230337

This is a simple example, but it can be taken much further.

Our pyro-mui-joy theme takes this to the extreme, with 4 variants (solid, outlined, soft, and neutral), 5 palettes (neutral, primary, success, info, and danger), and three sizes (sm, md, and lg) that can all be applied to a component to affect its look and feel.

20240901_230858

20240901_230921

When you get this scale though, handwriting the CSS classes becomes sadistic and you will want to turn to automation to generate them for you (maybe a topic for another time).

16 Likes

The most useful thing I can contribute is rather basic, but fundamental to playing with css:

step 1: Learn how to select a component: CSS selectors - CSS: Cascading Style Sheets | MDN
This doesn't mean memorize everything, it means learn how it works and how you can use a search engine to find the selectors you need.
step 2: Use the dev tools to find the element you want to style, then apply step 1 to target it.

That's where the "advanced" (meaning beyond perspective classes) css journey starts.

7 Likes

The browser devtools are your friend.
Its easy to find elements and from there you can craft selectors.
You can edit css in the devtools too to see if everything is working before copying the working code to perspective.

Adding in perspective classes given through the designer requires you to add:
"psc-" in front of the class name.
And if the class is in a folder the path "/" should become "\/"
example : .psc-Folder\/NewStyle

There are also some relativly new css selectors like :has() which are very powerful. Many sources who say its not possible in css, dont known about :has yet. :has() - CSS: Cascading Style Sheets | MDN

Also note not all css stuff works in the designer, but does show up correctly in browsers. That is because the designer is behind in chromium version. (And maybe the workstation app too i dont know)

5 Likes

I’ll plug our plugin for those who are generating CSS externally and find the perspective style class syntax error prone.

With this plugin,

[psc='Folder1/class'] {
    color: black;
}

[psc='Folder1/Folder2/class'] {
    color: black;
}

[psc='Folder1/class1'].otherClass1 .otherClass2 {
    color: black;
}

will be processed to:

.psc-Folder1\/class {
    color: black;
}

.psc-Folder1\/Folder2\/class {
    color: black;
}

.psc-Folder1\/class1.otherClass1 .otherClass2 {
    color: black;
}
4 Likes

Pulse shadow to draw attention to an element while the style is active.

.psc-pulse-shadow{
	animation: pulse-shadow .5s infinite;
}
@keyframes pulse-shadow {
  0% {
    	box-shadow: 0px 0px 5px 0px rgba(255,255,255,0.2);
  }
  100% {
  	box-shadow: 0px 0px 20px 20px rgba(255,255,255,0.5);
  }
}

4 Likes

Your post kickstarted a rework of how I theme things, so thanks for that.

I'll suggest one thing though: using color-mix to generate palettes based on a key color value.

For example, your danger palette could be written like this, basing the key value on the built-in --error:

--palette-danger:		var(--error);

--palette-error-0:		color-mix(in srgb, var(--palette-error) 0%, white);
--palette-error-50:		color-mix(in srgb, var(--palette-error) 10%, white);
--palette-error-100:	color-mix(in srgb, var(--palette-error) 20%, white);
--palette-error-150:	color-mix(in srgb, var(--palette-error) 30%, white);
--palette-error-200:	color-mix(in srgb, var(--palette-error) 40%, white);
--palette-error-250:	color-mix(in srgb, var(--palette-error) 50%, white);
--palette-error-300:	color-mix(in srgb, var(--palette-error) 60%, white);
--palette-error-350:	color-mix(in srgb, var(--palette-error) 70%, white);
--palette-error-400:	color-mix(in srgb, var(--palette-error) 80%, white);
--palette-error-450:	color-mix(in srgb, var(--palette-error) 90%, white);
--palette-error-500:	color-mix(in srgb, var(--palette-error) 100%, white);
--palette-error-550:	color-mix(in srgb, var(--palette-error) 90%, black);
--palette-error-600:	color-mix(in srgb, var(--palette-error) 80%, black);
--palette-error-650:	color-mix(in srgb, var(--palette-error) 70%, black);
--palette-error-700:	color-mix(in srgb, var(--palette-error) 60%, black);
--palette-error-750:	color-mix(in srgb, var(--palette-error) 50%, black);
--palette-error-800:	color-mix(in srgb, var(--palette-error) 40%, black);
--palette-error-850:	color-mix(in srgb, var(--palette-error) 30%, black);
--palette-error-900:	color-mix(in srgb, var(--palette-error) 20%, black);
--palette-error-950:	color-mix(in srgb, var(--palette-error) 10%, black);
--palette-error-1000:	color-mix(in srgb, var(--palette-error) 0%, black);

There are a few more nuances but don't mind those. Here's a comparison, the top one is using this palette, the bottom one is the danger palette you posted.

image

There might be a better color space to use with color-mix, or maybe it can be parameterized better, but I still have to dive into this.
Now, This may not be game changing for you, as you already have a full css setup and I believe, some processing to generate your css, but for someone who wants to start creating palettes, this can change the whole game, especially if you base your key colors on built-in colors, then the whole thing sticks to the theme without having to actually touch theme files.

edit:
error, warning, primary, success and neutral, based respectively on --error, --warning, --info, --success and --neutral-50

9 Likes

Excellent, I’ll absolutely be checking this out.

CSS only Rollers. Tiny repeated SVG inline CSS.

.psc-rollers-horizontal{
	background-image: url('data:image/svg+xml,<%3Fxml version="1.0" encoding="UTF-8"%3F><svg width="9.5201mm" height="45.916mm" version="1.1" viewBox="0 0 9.5201 45.916" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient id="linearGradient33" x1="330.51" x2="346.39" y1="19.993" y2="19.993" gradientTransform="scale(.40373 2.4769)" gradientUnits="userSpaceOnUse"><stop stop-color="%235a5a5a" offset="0"/><stop stop-color="%23fff" offset=".48578"/><stop stop-color="%235a5a5a" offset="1"/></linearGradient></defs><g transform="translate(-132.03 -46.269)"><rect x="135.8" y="48.147" width="1.6738" height="42.069" rx=".83739" ry=".50933"/><rect x="133.46" y="49.546" width="6.3606" height="39.279" rx=".83739" ry="2.1832" fill="url(%23linearGradient33)" stroke="%23505050" stroke-linecap="round" stroke-linejoin="round" stroke-width=".05"/></g><rect x="-1.4211e-14" width="9.5201" height="2.2318" rx="0" ry="0" fill="%23505050" stroke-width=".3206"/><rect x="-1.4211e-14" y="43.684" width="9.5201" height="2.2318" rx="0" ry="0" fill="%23505050" stroke-width=".3206"/></svg>');
	background-repeat: repeat-x;
	background-size: contain;
}

.psc-rollers-vertical{
	background-image: url('data:image/svg+xml,<%3Fxml version="1.0" encoding="UTF-8"%3F><svg width="45.916mm" height="9.5201mm" version="1.1" viewBox="0 0 45.916 9.5201" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient id="linearGradient33" x1="330.51" x2="346.39" y1="19.993" y2="19.993" gradientTransform="scale(.40373 2.4769)" gradientUnits="userSpaceOnUse"><stop stop-color="%235a5a5a" offset="0"/><stop stop-color="%23fff" offset=".48578"/><stop stop-color="%235a5a5a" offset="1"/></linearGradient></defs><g transform="rotate(90 112.11 -19.925)"><rect x="135.8" y="48.147" width="1.6738" height="42.069" rx=".83739" ry=".50933"/><rect x="133.46" y="49.546" width="6.3606" height="39.279" rx=".83739" ry="2.1832" fill="url(%23linearGradient33)" stroke="%23505050" stroke-linecap="round" stroke-linejoin="round" stroke-width=".05"/></g><rect transform="rotate(90)" y="-45.916" width="9.5201" height="2.2318" rx="0" ry="0" fill="%23505050" stroke-width=".3206"/><rect transform="rotate(90)" y="-2.2318" width="9.5201" height="2.2318" rx="0" ry="0" fill="%23505050" stroke-width=".3206"/></svg>');
	background-repeat: repeat-y;
	background-size: contain;
}

19 Likes

I'm also exploring the possibilities of the color() function, and the rgb(from {color}) syntax.
I'm not quite sure yet how I'll put them to use, but I'm sure there's potential.

1 Like

Adding this to the adv. stylesheet will allow popups to be resized dynamically. Without this, the size gets stuck when you're trying to reduce the size (I believe). Taken from here.

.ia_popup[id^="popup-"]{
    height: auto !important;
}

Also, setting the popup View's props.defaultSize.height: auto will auto-size the popup's height dynamically to its content which is quite useful.

17 Likes

While on expandable popups, using the same technique as above for expanding the width of popups has odd results. So for this, I add the same css for the height, but instead of setting the popup view width to auto, I explicitly set the props.defaultSize.width value with a binding which dynamically changes the width of the popup in the client.

CSS:

.ia_popup[id^="popup-"]{
    width: auto !important;
}

Edit: forgot to attach the demo:

There's a bit of a glitch where if the popup was set to open using auto positioning in the centre of the page and you haven't moved it, expanding it will continue to honour this. If you move it however, then the positioning becomes absolute and the popup will keep its top/left position when it's resized.

2 Likes

You can create new colours from existing colours, for example changing the alpha channel of existing colours, explicity setting any of the h, s, l / r, g, or b values, or applying calculations to them:

hsl(from var(--your-css-variable) h s l / 80%) # set the alpha channel to 80%
hsl(from var(--your-css-variable) h 30% l) # set the saturation of the colour to 30%
rgb(from aliceblue r g calc(b + 50)) # sets the blue value of the aliceblue colour to the blue value + 50 

Edit: I realise now that this is an elaboration of this!

5 Likes

This is golden! How did I not know this!

To what to do you bind the defaultSize.width?

1 Like

Usually when I want a popup to increase in width, it'll be because a user has pressed a button to expand it, so I would have view custom props called:

normalWidth: 400
expandedWidth: 600
currentWidth: "normal"

and then bind the default width to currentWidth, selecting either normal or expanded

2 Likes

I feel every time I revisit font sizing in Perspective, i'm constantly changing my thoughts.. I've gone from px to clamp(0px, calc(f*(1.0vmin + 1.0vmax)), 24px) to em to rem...
And now that I've cycled through the different units, I think I have to go back to clamp(0px, calc(f*(1.0vmin + 1.0vmax)), 24px) especially for coordinate containers set to percent, using this simple example:

Edit: I edited the above to add clamp to the calc, as you'll probably want to cap the size to not get too large (or small)

Edit2: clamp(0px, calc(f*(1.0vmin + 1.0vmax)), 24px) - f is a floating value, e.g. 0.8. Use this to control the font size. Use different values for your different standard font size variables.

8 Likes

What is f in this function?

f is just a placeholder for a value (float typically). Replace f with your value.

2 Likes