Closed jkelleyrtp closed 3 years ago
I think the system for styling would be best as a separate system, where individual properties are ideally strictly typed. Then the library could be integrated here.
I agree with @ThomasdenH and think this could start out as a separate crate but could eventually get added to yew
later as it matures, perhaps in the form of "styled components".
@jkelleyrtp are you thinking of taking approach like https://cssinjs.org/? I would love having a way to use declarative styles like this. Then the integration with yew
is as simple as setting the class name
@jstarry I was definitely feeling inline CSS in the same way that JSS works - I like having the state of the component directly tied to its appearance. I'm going to approach this in the same way that styled-components work so we can just drop existing stylesheets into components but also be able to modify them on component updates.
Personally I just use bootstrap. Then apply the class attributes as needed: https://hackerthemes.com/bootstrap-cheatsheet/
I don't use the JS part of it; just the stylesheets.
I can imagine possibly a high level API that just applies those, but I think I would prefer to just apply them myself.
The question is where does the scope of yew end ? perhaps what's needed is a separate yew-styling plugin/library ?
@jkelleyrtp Have you made any progress towards this? I have a loosely defined proposal on how to implement this typed up and would love to give it a shot in building it, but if you have something in the works, I would rather hold off and wait on that.
Nevermind, I've got a prototype in the works here in case anyone wants to check it out: https://github.com/hgzimmerman/yew_css
Here is my thoughts on building styling system.
I think there are two different places that we need to handle style:
Every component can have it's own default style, let's say a Button
component have this default style:
color: black;
background: white;
border: 1px solid black;
border-radius: 4px;
At this point we need to defined the behavior of the styling system, thus we need to answer these questions:
Some times we change style for component instate, by adding, removing .. etc styles to the component instate, thus we need to defined a how the styling system would allow us to do that.
How the styling system will get these default styles? and from where?
By using trait Theme
that would return Style
struct that contains CssName
and CssValue
pairs, for the component. Every theme struct would implement this trait.
Theme
that uses classesThis trait uses the class
to construct Style
for it. this trait is simple to implement but doesn't have full access to the component state. it also can have styles even if we have no components for them, it is easy to reuse existing css libraries with this trait.
pub trait Theme {
fn style(&self, class: impl Into<Cow<'static, str>>) -> Option<Style>;
}
// example
pub struct DefaultTheme;
impl Theme for DefaultTheme {
fn style(&self, class: impl Into<Cow<'static, str>>) -> Option<Style> {
match class.into().as_ref() {
"button" => unimplemented!(),
"flexbox" => unimplemented!(),
}
}
}
Theme
uses components stateThis trait uses the component state to build Style
for it, it have full access to the component state, but it's not simple to implement as well as it's only support components that are present in Component
enum.
pub trait Theme<Comps> {
fn style(&self, class: impl Into<Comps>) -> Option<Style>;
}
// example
pub struct DefaultTheme;
impl<'a> Theme<Components<'a>> for DefaultTheme {
fn style(&self, comp: impl Into<Components<'a>>) -> Option<Style> {
match comp.into() {
Components::Button(btn) => unimplemented!(),
Components::Flexbox(flexbox) => unimplemented!(),
}
}
}
pub enum Components<'a> {
Button(&'a Button),
Flexbox(&'a Flexbox),
}
This my try, for sure there are a lot of things that need to be changed for these ideas to fit in Yew. I didn't answer the other questions, since I don't have clear answer for them.
I'm having a play with writing a css parser from rust tokens, and it seems to be going well. It's available at https://github.com/derekdreery/style. The idea is that you can do things like
<div style={{font-size: 10px; flex-direction: column}}></div>
from within a macro in rust code, making using css/styles feel really natural.
Wow, awesome @derekdreery! Looks like the yew html macro could depend on your style macros under the hood. Is that what you had in mind?
I agree with
individual properties are ideally strictly typed
from https://github.com/yewstack/yew/issues/533#issuecomment-513581931.
Looks like the yew html macro could depend on your style macros under the hood. Is that what you had in mind?
Happy to help out however you want to use them.
There are a couple of small caveats:
color: #"fff"
)e
(this is because the lexer tries to interperet the number as a float with an exponent). (e.g. font-size: 1"em"
)individual properties are ideally strictly typed
The lib I'm working on does typecheck your CSS, so only valid types of property values are allowed for a given property name.
My preference is to use css modules, or css that's localised to the component, keeping components from conflicting with each other automatically. To this end there is the css-modules crate, which simply aliases all of the class names in a css file and imports a list of those aliases into rust.
The author of the crate was recently harassed into deleting their GitHub account by a group of transphobes but everything still works just fine.
Edit: I forgot to say that I also use it with rollup for post processing.
First off, New to rust & New to yew... Figure it would be a great place to learn both!
Recently been using a lot of Svelte and I have really enjoyed their take on styling.
<style>
p {
color: purple;
font-family: 'Comic Sans MS', cursive;
font-size: 2em;
}
</style>
<p>Styled!</p>
They do stuff under the hood of generating classes and attaching them to the elements but it avoids concepts of theming and leaves it to the developer/css.
Ideally (for me) an API like so would exist like so where I could leverage css variables to do the theming/customization of a child component in CSS.
impl Component for Model {
fn style(&self) -> Css {
css! {
div {
background: black;
--main-text-color: white;
}
}
}
fn view(&self) -> Html {
html! {
<div>
<MyComponent /> <!-- changes child's --main-text-color by setting the var -->
</div>
}
}
It may not be possible (still learning) but I would be interested if it does.
@bl42 I like the look of that.
Minor detail: the function signature wouldn't quite work like that if the CSS was generated at compile time since we wouldn't have an instance of the component then.
Ideally (for me) an API like so would exist like so where I could leverage css variables to do the theming/customization of a child component in CSS.
I don't quite understand your vision here, could you elaborate a bit more?
I have developed trait with similar functionality, Component should declare their Style struct that will be used to style the component (e.g. Entry) instead of using some sort of collection that doesn't help at checking the used style if it works with the Component or not.
My project using Seed, but I'm pretty sure this idea is applicable in Yew too.
I don't quite understand your vision here, could you elaborate a bit more?
My preferred output would have some "Global Styles" that I can declare for the app (on mount)
Something like
@import 'google fonts...'
@import 'css animations lib...'
html {
font-size: 65.25%
--primary-color: #000;
--primary-text: #fff;
etc..
}
and in each component have the ability to call color: var(--primary-text);
We could easily store these values in Rust... but I suggest to let CSS do what it is good at.
Hi I just published a library that is supposed to help with that. I was in need for a framework that works in a way that styled components work. Here is what I came up with: CSSinRust https://crates.io/crates/css-in-rust. Feel free to open Issues and/or PR's. The target is to make it work with any components framework.
Since this PR's title is brainstorming. I'll throw in my 5 cents with Components as a way to hide style implementation details.
Libraries like Styled Components enable developers to bind style at a Component level, which leads to the removal of the class to node mapping. It also enforces more declarative JSX
over prop controls, when done correctly.
In JS, template literals give the ability to evaluate the Component's CSS declaration (a string mixed with expressions) through the components own props. I guess in rust a macro would do this instead.
These styles are added to the <head>
of the document as <style>
tags, enabling any CSS capabilities you'd like.
I disagree with having defaults for styles, because the browser already takes care of that. One should also consider, how could I reset, or normalize, or simply set some global properties on my stylesheet? What about prefixes? Perhaps this is out of the scope of Yew.
@icyJoseph this sounds good. It would be nice to typecheck the styles at compiletime then generate the appropriate string templates for use at runtime.
Similar to @icyJoseph I'm just throwing my ideas at the wall here, but as a web developer my experience is that developers are embracing tools such as styled components as it means everything is "just JS" - meaning one can handle dynamic prop based styles intuitively whilst the clear component scoping reduces the classic "why has my div flew half down the screen I only want the title to be red".
I believe the "just JS" approach to styling in yew ( "just rust" ) and building a rust-y implementation of "styled-components"-ish style system thats fits the needs of yew could lead to not only a more consistent developer experience due to projects being only one language but also gives oppurtunity to build more advanced tooling around styles be that in the editor or oppurtunity for minimising style size at build. The things styled components does well and has brought to the ecosystem, are almost dying to implemented in the rust.
I would also like to say I would like to keep an eye on this and am happy to discuss further and get involved in any proof of concepts. Not sure the etiquette here but if I wanted to start work on this to see how it would work, would I need for this discussion to be resolved or would I need to create some code and build a discussion around a PR. Thanks for any pointers.
What if we use something like Rust structs and enums, something like:
style = Style {
BackgroundColour(Colour::Black),
Colour(Colour::RGB(140,140,140),
.. Style::default()
}
Rather than going for parsing or macros, I feel this might be better.
I like the way material UI supported styles (it's CSS framework for react): https://material-ui.com/styles/basics/
They are using type definitions for css properties from https://github.com/frenic/csstype (it's for typescript) and use it in functions like createStyles, where user defines named classes (similar to css classes) and then can use those named classes later in tsx template.
They also allow theming, when createStyles call wrapped to makeStyles, which receive (contextual) theme.
I think it's a great example and it would be cool to have something similar for yew. The most complicated part in this is (I think) typing all of css properties in Rust types, but that work worth it.
I like the idea behind StyledComponents but in practice, it's just pure pain.
The nicest approach to CSS I saw so far is bs-css that is Statically typed DSL for writing css in reasonML
It's similar to what @emmanuelantony2000 proposed. Instead of enums reasonML have variadic types and polymorphic variant types (sure it's not the same as Enums, I'm not saying it can be easily replicated in Rust). For example:
open Css;
let card = style([
display(flexBox),
flexDirection(column),
alignItems(stretch),
backgroundColor(white),
boxShadow(Shadow.box(~y=px(3), ~blur=px(5), rgba(0, 0, 0, 0.3))),
unsafe("-webkit-overflow-scrolling", "touch"),
padding(Theme.basePadding)
]);
(all definitions are imported by opening Css module) And then it can be easily reused as a class:
<div className=card></div>
The only bs-css
cons I found so far is that classes names are generated quite randomly so it's still hard to navigate through HTML elements.
Besides that typed system for CSS is super satisfying to write stylings.
^ related prior art: https://github.com/purescript-contrib/purescript-css
A couple of observations:
cursor: pointer; cursor: hand;
was an old idiom for cross-browser cursor support. The complexity here would best be handled by offloading to an existing library like libsass
during compilation, and unfortunately I think this means that plain Rust structs (per @emmanuelantony2000 's comment) would not be able to represent the full range of CSS someone could write. We could of course still support Rust structs as well, but it wouldn't cover the full gamut if you will.Just saw this on my feed today, a Sass compiler built in pure Rust: https://github.com/connorskees/grass
CSSinRust is now compatible with yew 0.17.* (https://crates.io/crates/css-in-rust). Check out https://github.com/lukidoescode/yew-fullstack-boilerplate on how to use it. I'd appreciate any PRs.
Just saw this on my feed today, a Sass compiler built in pure Rust: https://github.com/connorskees/grass
This looks great. Is there a way of providing a sass or css file that overrides these defaults? It would be great to give to an artist for them to tweak.
How about the way vueJS does it: https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors
You could add an unique id to every component, modify the css which should be pretty simple, and recursively add an attribute to every DOM element in the component, perhaps at render time.
My dream is to have a component-oriented solution like Yew, but to be able to use some custom styling/layout language instead of HTML/CSS, something like iced has, or like elm-ui. The biggest issue I see with CSS is that styles are separated from HTML and the view is splitted into two parts as a result. You can also take a look at elm-css that solves this by allowing you to embed CSS directly into HTML and then auto-generates classes from that, but I don't believe that's the right direction. I'm convinced that the better solution is to build abstractions over HTML/CSS instead (that maybe could then even compile to other platforms like in the case of iced).
I haven't tried Yew yet actually, just trying to contribute to the discussion by bringing some ideas :)
I have one possible solution: https://github.com/lovelace-ed/lovelace/blob/main/utils/mercutio/src/lib.rs
Note that it depends on Malvolio (another crate I've written) and I can't publish the next version of that until Rocket cuts its next release 🤷🏼
Reading this conversation, the bs-css was the best implementation example, I think.
One more thing to consider is that the browser downloads the whole wasm code at once. As far as I know, yew cannot split bundles currently. So maybe it's not the best performance to keep the CSS in rust right now. Also, CSS in WASM requires two compilations, one for WASM, one for CSS. As I see, currently the css-modules is the best performing option with in-rust syntax checking.
I think I'm approaching the "styling" question from a slightly different angle. "CSS-in-<language>" may be a good fit for modular aesthetic/layout styling, but it seems a bit heavy-handed for data-driven styles where object-identity of an element/component matters more than its semantic-identity.
For example, I'm currently working on a Resizable
component (vastly simplified MVP based on re-resizable) that is most simply implemented in React with the style
prop. Creating a new style tag (or diffing into one) seems like a lot of overhead to just "update width/height of this specific element in response to mouse/touch event shenanigans". Inline, element-level styles can easily be abused, I'm not disputing that. But I think they still have a place.
The obvious workaround is to use NodeRef
s and mutate styles in update
(or use js-sys
/external .js to wire events directly, using yew
only for bootstrapping). This is tempting, but you give up the advantages of pure unidirectional flow. You also have to special-case your initial render, because your NodeRef
s don't exist yet. You may also have to do a lot of bookkeeping if elements-with-styles can pop in/out of existence based on Props
(e.g. if "what edges/corners are draggable" can change dynamically).
Handling a style
prop along the lines of the current html!
macro system sounds like a bit of a nightmare. Striking a balance between static verification, ergonomics, and performance seems seriously nontrivial. Maybe it's not worth it to support use-cases like mine? But, just wanted to throw it out there, because it seems like an angle otherwise missing from the discussion.
Composable styles are becoming popular, via https://tailwindcss.com/
Today I can use the twind shim in a Yew app and get Tailwind support for free, but a proper CSS compiler that generates only the classes in use inside the html!
macro (like postcss does in the JS world) woud be nice. See also https://windicss.org/ for some ideas (it compiles and replaces the N-classes into a single class).
Tailwindcss JIT compiler https://tailwindcss.com/docs/just-in-time-mode
Since there have been no commits on the css-in-rust crate for over a year, I have decided to create a fork to provide Yew 0.18 support and fix a couple known issues that I had when using the crate as well as some improvement on the internals.
https://github.com/futursolo/stylist-rs
I would appreciate any feedback on this.
I'd like to add a styling system to yew - not having a built-in way of styling components seems a bit unfinished to me. If we want to build better, bigger, and faster web apps, then we need a way of managing styles that doesn't clobber a global namespace and makes sense when looking at the components.
I'd like to see a Stylable trait where we can pull in an existing stylesheet (using css-parser for the servo project), modify it (override the defaults), and then set that as the element style.
We could also have a special style property for each element where we can inject style into even the subdivs of elements.
I personally favor parsing an external stylesheet and doing inline modification because of autocompletes working with .css files, but there is also room for a css! macro and reserve a keyword for the element that the style is being applied to, to access subdivs in that element.
Curious what the thoughts of the community is before I try pull my changes into the project.