A simple solution for scoped styles in Leptos

Related tags

Command-line styled
Overview

Styled: Easy Styling for Leptos Components

If you're looking for an easy way to apply scoped styles to your Leptos components, Styled is the Leptos macro you need. With Styled, you can apply high-level selectors like button or div to specific components, keeping your markup clean and organized.

Installation

Use cargo add in your project root

cargo add styled stylist

Usage

First create a basic Leptos component. This will serve as the foundation for this little guide.

#[component]
pub fn MyComponent(cx: Scope) -> impl IntoView{
  view! {
    cx,
    <div>"hello"</div>
  }
}

Next, import the style macro, powered by an awesome crate called Stylist, to create your styles. Just add this to the top of your file.

use styled::style;

You can then use the style macro to create a Result containing your styles. Let's modify our component:

#[component]
pub fn MyComponent(cx: Scope) -> impl IntoView{
  
  let styles = style!(
    div {
      background-color: red;
      color: white;
    }
  );

  view! {
    cx,
    <div>"hello"</div>
  }
}

Now, let's apply those styles with our styled::view! macro!

#[component]
pub fn MyComponent(cx: Scope) -> impl IntoView {

    let styles = style!(
      div {
        background-color: red;
        color: white;
      }
    );

    styled::view! {
        cx,
        styles,
        <div>"This text should be red with white text."</div>
    }
}

Now we can define another component that also uses the div CSS selector but it's styles will only apply to the elements inside of it's enclosing styled::view! macro.

#[component]
pub fn AnotherComponent(cx: Scope) -> impl IntoView {

    // note were using a plain div selector and it wont clash with MyComponent's div style!
    let styles = style!(
      div {
        background-color: blue;
        color: gray;
      }
    );

    styled::view! {
        cx,
        styles,
        <div>"This text should be blue with gray text."</div>
    }
}

Longer Example

// /src/components/button.rs

use crate::theme::get_theme;
use leptos::*;
use styled::style;

#[derive(PartialEq)]
pub enum Variant {
    PRIMARY,
    SECONDARY,
    ALERT,
    DISABLED,
}

impl Variant {
    pub fn is(&self, variant: &Variant) -> bool {
        self == variant
    }
}

struct ButtonColors {
    text: String,
    background: String,
    border: String,
}

fn get_colors(variant: &Variant) -> ButtonColors {
    let theme = get_theme().unwrap();
    match variant {
        Variant::PRIMARY => ButtonColors {
            text: theme.white(),
            background: theme.black(),
            border: theme.transparent(),
        },
        Variant::SECONDARY => ButtonColors {
            text: theme.black(),
            background: theme.white(),
            border: theme.gray.lightest(),
        },
        Variant::ALERT => ButtonColors {
            text: theme.white(),
            background: theme.red(),
            border: theme.transparent(),
        },
        Variant::DISABLED => ButtonColors {
            text: theme.white(),
            background: theme.red(),
            border: theme.transparent(),
        },
    }
}

#[component]
pub fn Button(cx: Scope, variant: Variant) -> impl IntoView {
    let disabled = variant.is(&Variant::DISABLED);

    let styles = styles(&variant);

    styled::view! {
        cx,
        styles,
        <button disabled=disabled>"Button"</button>
    }
}

fn styles<'a>(variant: &Variant) -> styled::Result<styled::Style> {
    let colors = get_colors(variant);

    style!(
            button {
                color: ${colors.text};
                background-color: ${colors.background};
                border: 1px solid ${colors.border};
                outline: none;
                height: 48px;
                min-width: 154px;
                font-size: 14px;
                font-weight: 700;
                text-align: center;
                box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
                position: relative;
                box-sizing: border-box;
                vertical-align: middle;
                text-align: center;
                text-overflow: ellipsis;
                text-transform: uppercase;
                overflow: hidden;
                cursor: pointer;
                transition: box-shadow 0.2s;
                margin: 10px;
            }

            & button:active {
                transform: scale(0.99);
            }


            & button::-moz-focus-inner {
                border: none;
            }

            & button::before {
                content: "";
                position: absolute;
                top: 0;
                bottom: 0;
                left: 0;
                right: 0;
                background-color: rgb(255, 255, 255);
                opacity: 0;
                transition: opacity 0.2s;
            }

            & button::after {
                content: "";
                position: absolute;
                left: 50%;
                top: 50%;
                border-radius: 50%;
                padding: 50%;
                background-color: ${colors.text};
                opacity: 0;
                transform: translate(-50%, -50%) scale(1);
                transition: opacity 1s, transform 0.5s;
            }

            & button:hover,
            & button:focus {
                box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12);
            }

            & button:hover::before {
                opacity: 0.08;
            }

            & button:hover:focus::before {
                opacity: 0.3;
            }

            & button:active {
                box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
            }

            & button:active::after {
                opacity: 0.32;
                transform: translate(-50%, -50%) scale(0);
                transition: transform 0s;
            }

            & button:disabled {
                color: rgba(0, 0, 0, 0.28);
                background-color: rgba(0, 0, 0, 0.12);
                box-shadow: none;
                cursor: initial;
            }

            & button:disabled::before {
                opacity: 0;
            }

            & button:disabled::after {
                opacity: 0;
            }

    )
}
// /src/theme/mod.rs
use csscolorparser::Color;

pub fn get_theme() -> Result<Theme, csscolorparser::ParseColorError> {
    let theme = Theme {
        teal: Colors {
            main: Color::from_html("#6FDDDB")?,
            darker: Color::from_html("#2BB4B2")?,
            lighter: Color::from_html("#7EE1DF")?,
            lightest: Color::from_html("#B2EDEC")?,
        },
        pink: Colors {
            main: Color::from_html("#E93EF5")?,
            darker: Color::from_html("#C70BD4")?,
            lighter: Color::from_html("#F5A4FA")?,
            lightest: Color::from_html("#FCE1FD")?,
        },
        green: Colors {
            main: Color::from_html("#54D072")?,
            darker: Color::from_html("#30AF4F")?,
            lighter: Color::from_html("#82DD98")?,
            lightest: Color::from_html("#B4EAC1")?,
        },
        purple: Colors {
            main: Color::from_html("#8C18FB")?,
            darker: Color::from_html("#7204DB")?,
            lighter: Color::from_html("#B162FC")?,
            lightest: Color::from_html("#D0A1FD")?,
        },
        yellow: Colors {
            main: Color::from_html("#E1E862")?,
            darker: Color::from_html("#BAC31D")?,
            lighter: Color::from_html("#EFF3AC")?,
            lightest: Color::from_html("#FAFBE3")?,
        },
        gray: Colors {
            main: Color::from_html("#4a4a4a")?,
            darker: Color::from_html("#3d3d3d")?,
            lighter: Color::from_html("#939393")?,
            lightest: Color::from_html("#c4c4c4")?,
        },
        red: Color::from_html("#FF5854")?,
        black: Color::from_html("#000000")?,
        white: Color::from_html("#FFFFFF")?,
        transparent: Color::from_html("transparent")?,
    };

    Ok(theme)
}

pub struct Theme {
    pub teal: Colors,
    pub pink: Colors,
    pub green: Colors,
    pub purple: Colors,
    pub yellow: Colors,
    pub gray: Colors,
    pub red: Color,
    pub black: Color,
    pub white: Color,
    pub transparent: Color,
}

pub struct Colors {
    pub main: Color,
    pub darker: Color,
    pub lighter: Color,
    pub lightest: Color,
}

impl Colors {
    pub fn main(&self) -> String {
        self.main.to_hex_string()
    }
    pub fn darker(&self) -> String {
        self.darker.to_hex_string()
    }
    pub fn lighter(&self) -> String {
        self.lighter.to_hex_string()
    }
    pub fn lightest(&self) -> String {
        self.lightest.to_hex_string()
    }
}

impl Theme {
    pub fn red(&self) -> String {
        self.red.to_hex_string()
    }
    pub fn black(&self) -> String {
        self.black.to_hex_string()
    }
    pub fn white(&self) -> String {
        self.white.to_hex_string()
    }
    pub fn transparent(&self) -> String {
        self.transparent.to_hex_string()
    }
}
// /src/app.rs

#[component]
fn HomePage(cx: Scope) -> impl IntoView {
    view! { cx,
            <Button variant={button::Variant::PRIMARY}/>
            <Button variant={button::Variant::SECONDARY}/>
            <Button variant={button::Variant::ALERT}/>
    }
}
You might also like...
A website for the Leptos Web Framework!

Leptos Website A Repo for the public facing Leptos website. Still a WIP. Client Side Rendering This example cannot be built as a trunk standalone CSR-

A Nix template for full-stack web apps in Rust using Leptos

leptos-fullstack A Nix template for full-stack web apps in Rust using Leptos. Tech used: Leptos full-stack framework server functions ssr + hydration

A zero-config leptos component to display markdown
A zero-config leptos component to display markdown

A port of yew-markdown using leptos ! Usage You can use this component to render both static and dynamic markdown. Static markdown use leptos::*; {

Experimental integration of `fedimint-client` with the Leptos web frontend framework
Experimental integration of `fedimint-client` with the Leptos web frontend framework

CAUTION: highly experimental, the Database implementation is likely horribly broken Fedimint Client built with Leptos This repo contains a proof-of-co

Leptos server signals synced through Server-Sent-Events (SSE)

Leptos Server Sent Events Server signals are leptos signals kept in sync with the server through server-sent-events (SSE). The signals are read-only o

Leptos integration with Golden Layout.

Leptos Golden Layout Leptos integration with Golden Layout. This has many features missing but covers the basics for integrating leptos with golden la

A UI component library for Leptos, based on Tailwind Elements.

leptos-twelements A UI component library for Leptos, based on Tailwind Elements. Installation (for projects using cargo leptos) Use the nightly rust c

A binary that bootstraps a Leptos application with client side rendering, tailwind, and vercel

create-leptos-csr-tw This CLI provides a quick setup to start building web applications using the Leptos web framework integrated with TailwindCSS. It

💫 List-rendering component utilizing FLIP position transitions for Leptos
💫 List-rendering component utilizing FLIP position transitions for Leptos

AnimatedFor / component for Leptos FLIP animations for element and component groups inspired by Vue's TransitionGroup. This crate exports a compon

Owner
Eran Boodnero
Eran Boodnero
Semi-persistent, scoped test directories

Semi-persistent, scoped test directories This crate aims to make it easier to use temporary directories in tests, primarily by calling the testdir!()

Floris Bruynooghe 4 Nov 19, 2022
A quick-and-dirty attempt to get scoped tasks in Rust.

scoped_tasks_prototype A quick-and-dirty attempt to get scoped tasks in Rust. This library tries to provide an interface similar to scoped threads, ac

Olivier FAURE 8 Jun 6, 2023
Rust library for ANSI terminal colours and styles (bold, underline)

rust-ansi-term This is a library for controlling colours and formatting, such as red bold text or blue underlined text, on ANSI terminals. View the Ru

Benjamin Sago 407 Jan 2, 2023
A CLI utility installed as "ansi" to quickly get ANSI escape sequences. Supports the most basic ones, like colors and styles as bold or italic.

'ansi' - a CLI utility to quickly get ANSI escape codes This Rust project called ansi-escape-sequences-cli provides an executable called ansi which ca

Philipp Schuster 5 Jul 28, 2022
colorStyle is a library of styles for command-line text write in Rust.

Colorstyle colorStyle is a library of styles for command-line text. Inspired by flylog/colorstyle (golang) Example let text = colorstyle::green("gre

Code Translation 6 Nov 12, 2022
omekasy is a command line application that converts alphanumeric characters in your input to various styles defined in Unicode.

omekasy is a command line application that converts alphanumeric characters in your input to various styles defined in Unicode. omekasy means "dress up" in Japanese.

null 105 Nov 16, 2022
Simple template to use csr and ssr leptos with tauri for ios/android/windows/macos/linux and web dev

Tailwind-Leptos-Tauri Template Simple template to use csr and ssr leptos with tauri for ios/android/windows/macos/linux and web dev Just clone the rep

Victor Batarse 11 Mar 10, 2024
My solution for the advent of code 2021, mainly written in Rust

Advent-of-Code-2021 My solution for the advent of code 2021, written in Rust Error Handle NOPE!!! unwrap() everything everywhere Use To run all of the

Nguyen Le Duy 0 Jan 8, 2022
A lightning fast networking solution for roblox.

Zap Zap is a blazingly fast networking solution for Roblox. Features Zap packs data into buffers with no overhead. The same data can be sent using a f

null 18 Dec 31, 2023
A formatter for the leptos view! macro

leptosfmt A formatter for the leptos view! macro All notable changes are documented in: CHANGELOG.md Install cargo install leptosfmt or for trying out

Bram 13 Apr 4, 2023