This tutorial follows the quick start guide on scoped styles.
Scoped styles are styles specific to a component, and are automatically applied to that component whenever it's used. Scoped styles are great for keeping things consistent and easy to manage, because they make sure a component's style is only applied to that component.
The tutorial will use the same setup as the first project tutorial. Basically, we create an empty folder, and then use lvweb
inside the folder to generate a starting project:
mkdir routes
cd routes
npm i live-elements-web-cli
npx lvweb init
npm i
lvweb serve
To check, we can go to localhost:8080, and we should see the following text:
Welcome to Live Elements
From here we can start building with scoped style components.
One of the biggest advantages of components with scoped styles is that they are ready to be used, since they provide a starting default style that makes the component functional. The live-web-view
module has a large collection of these components.
For example, ColLayout
and Col
are part of live-web-view.layout
module, and are used to create a column layout. Let's see how they work. We can add them to app/Home.lv
:
import live-web.dom
import live-elements-web-server.view
import live-elements-web-server.style
import live-web-view.layout
component Home < PageView{
head: PageProperties{ title: "Live Elements" }
Body{
ColLayout{
Col{
H1`Column 1`
}
Col{
H1`Column 2`
}
}
}
}
If we refresh the page at localhost:8080 we will see the newly added text, but won't see the actual layout split into columns. This is because the server is unaware of the additional styles included in our application. In order for the server to know about our styles, we need to define a use
property inside the PageView
component:
import live-web.dom
import live-elements-web-server.view
import live-elements-web-server.style
import live-web-view.layout
component Home < PageView{
head: PageProperties{ title: "Live Elements" }
static any[] use = [
ColLayout
]
Body{
ColLayout{
Col{
H1`Column 1`
}
Col{
H1`Column 2`
}
}
}
}
The use
property is a static property (we do this so the server doesn't have to create the component) that lets the server know what components with scoped styles are being used on the page. The server will collect these components together with their styles, and generate a stylesheet that includes styles for all the collected components. Notice we also took out the global stylesheet. The server will link the necessary styles to this component automatically. To see the new changes we need to restart the server:
npx lvweb serve
Refreshing the page, we should see the 2 column layout now working.
Let's add a form. live-web-view.form
contains an already styled form component:
import live-web.dom
import live-elements-web-server.view
import live-elements-web-server.style
import live-web-view.layout
import live-web-view.form
component Home < PageView{
head: PageProperties{ title: "Live Elements" }
static any[] use = [
ColLayout,
FormContainer
]
Body{
ColLayout{
Col{
FormContainer{
FormGroup{
TextInput{ type='email' placeholder="Email Address" name="email" }
FormGroup{
}
TextInput{ type='password' placeholder="Password" name="password" }
}
FormGroup{
SubmitButton{ T`Submit` }
}
}
}
Col{
H1`Column 2`
}
}
}
}
FormContainer
applies a style not only to itself, but also to all of it's children. This is why children like TextInput
will have a different style inside FormContainer
.
The easiest way to understand how components with scoped styles work is to create actually create one. Let's create a component that displays information about the form we have created. The column layout can be used to show the form in the right column, and information about the form in the left column. We can call our component FormInformation
. We'll create it inside a new folder in the root of the project called views
.
Let's create the folder in the root of our project:
mkdir views
And then create a file views/FormInformation.lv
:
import live-web.dom
import live-elements-web-server.style
component FormInformation < Div{
classes: [ScopedStyle.className(FormInformation)]
H1`Login Form`
P`You can use this form to login. Provide a valid email address and password in order to login to the
system.`
}
We extend a Div
container, then add a heading (H1
) and pararaph (P
). We also set the class for the component, which is assigned using ScopedStyle.className(FormInformation)
, a value that ScopedStyle
will capture from the server.
FormInformation
will also need a css file for it's styles. We can create views/forminformation.css
file next to it:
&FormInformation{
padding: 10px;
color: #444;
}
&FormInformation h1{
margin: 0;
font-weight: bold;
text-align: left;
font-family: sans-serif;
}
&FormInformation p{
margin: 0;
text-align: left;
font-family: sans-serif;
}
&FormInformation
is a special syntax used by this file in order to reference any FormInformation
component. Inside FormInformation
we style the heading and paragraph components.
We can also omit &FormInformation
from p
and h1
since it will be prefixed automatically to all elements in this stylesheet:
&FormInformation{
padding: 10px;
color: #444;
}
h1{
margin: 0;
font-weight: bold;
text-align: left;
font-family: sans-serif;
}
p{
margin: 0;
text-align: left;
font-family: sans-serif;
}
Even though &FormInformation
selector has been left out from p
and h1
, it will be inserted automatically by the server when parsing this file, therefore not conflicting with other p
and h1
elements in our page.
We now need to let the server know that FormInformation
component will use the css file we created. We can do this through the static use
property:
import live-web.dom
import live-elements-web-server.style
component FormInformation < Div{
static any[] use = [ScopedStyle{ src: './forminformation.css' }]
classes: [ScopedStyle.className(FormInformation)]
H1`Login Form`
P`You can use this form to login. Provide a valid email address and password in order to login to the
system.`
}
The src
property from ScopedStyle
sets a relative path to the forminformation.css
from the path to FormInformation
component.
Now we can use FormInformation
inside the main page just like we did FormContainer
:
import live-web.dom
import live-elements-web-server.view
import live-elements-web-server.style
import live-web-view.layout
import live-web-view.form
import .views
component Home < PageView{
head: PageProperties{ title: "Live Elements" }
static any[] use = [
ColLayout,
FormContainer,
FormInformation
]
Body{
ColLayout{
Col{
FormContainer{
FormGroup{
TextInput{ type='email' placeholder='Email Address' name='email' }
}
FormGroup{
TextInput{ type='password' placeholder='Password' name='password' }
}
FormGroup{
SubmitButton{ T`Submit` }
}
}
}
Col{
FormInformation{}
}
}
}
}
ScopedStyle
supports custom processors to parse its css file. For example, components in live-web-view
package use ScopedStyle
swith tailwind css processor in order to use tailwind classes in css with the @apply directive.
ScopedStyle
has a property called process
which accepts a link to a component that will do the processing.Tailwind processor is for example defined in live-web-view/style/CSSProcessor.lv
. So a ScopedStyle
using this processor would point the process
property to that location:
ScopedStyle{
process: 'live-web-view/style/CSSProcessor.lv'
}
To better understand this, let's use the tailwind processor in FormInformation
component. We need to do 2 things. Configure the process
property and include the global tailwind stylesheet file. The global tailwind stylesheet configures the main tailwind directives required for tailwind to work:
import live-web.dom
import live-elements-web-server.style
component FormInformation < Div{
static any[] use = [
ScopedStyle{ src: './forminformation.css' process: 'live-web-view/style/CSSProcessor.lv' },
ScopedStyle{ src: 'live-web-view/style/global.css' process: 'live-web-view/style/CSSProcessor.lv' }
]
classes: [ScopedStyle.className(FormInformation)]
H1`Login Form`
P`You can use this form to login. Provide a valid email address and password in order to login to the
system.`
}
Although this stylesheet is included for every component that uses this processor, the server makes sure to remove any duplicates and include it only once.
In forminformation.css
file, we can now use the @apply
directive:
&FormInformation{
@apply p-4 text-[#444444];
}
h1{
@apply m-0 font-bold text-left font-sans;
}
p{
@apply m-0 text-left font-sans;
}
Components with scoped styles can be nested within other components. This allows you to consolidate multiple components into a single one that contains all the others, simplifying the project's structure and making it easier to manage. The main page for example now uses FormContainer
ColLayout
and FormInformation
. We can wrap all of these inside a single reusable component.Let's create a wrapper component called DescriptiveForm
. The component will inherit from ColLayout. We will put it inside views/DescriptiveForm.lv
:
import live-web.dom
import live-web-view.layout
import live-web-view.form
import live-elements-web-server.style
component DescriptiveForm < ColLayout{
static any[] use = [
FormContainer,
FormInformation
]
classes: [ScopedStyle.className(DescriptiveForm)]
Col{
FormContainer{
FormGroup{
TextInput{ type='email' placeholder='Email Address' name='email' }
}
FormGroup{
TextInput{ type='password' placeholder='Password' name='password' }
}
FormGroup{
SubmitButton{ T`Submit` }
}
}
}
Col{
FormInformation{}
}
}
Since we're inheriting from ColLayout
we don't need to include it in the use
property. FormDescription
and FormInformation
however have to be declared.
Now, the main page can simply use the DescriptiveForm
, and FormInformation
and the rest will be included automatically:
import live-web.dom
import live-elements-web-server.view
import live-elements-web-server.style
import .views
component Home < PageView{
head: PageProperties{ title: "Live Elements" }
static any[] use = [
DescriptiveForm
]
Body{
DescriptiveForm{}
}
}
We can also overwrite styles in DescriptiveForm
for all its used components. Let's create views/descriptiveform.css
and reference it in the use
property:
import live-web.dom
import live-web-view.layout
import live-web-view.form
import live-elements-web-server.style
component DescriptiveForm < ColLayout{
static any[] use = [
FormContainer,
FormInformation,
ScopedStyle{ src: './descriptiveform.css' }
]
// ...
}
In descriptiveform.css
, we can select any component used by DescriptiveForm
:
&DescriptiveForm{
padding: 10px;
}
&DescriptiveForm &Col{
border: 1px solid #ccc;
}
The selectors will automatically be converted by the server to match the components. We can refresh the page to see the final result.
The same way we used css component selectors from descriptiveform.css
we can also use them from PageView
and apply the selectors to any component used by PageView
. To use these types of selectors, we need to change the way the stylesheet file is referenced by PageView
, and add it to the use
property instead.
import live-web.dom
import live-elements-web-server.view
import live-elements-web-server.style
import .views
component Home < PageView{
head: PageProperties{ title: "Live Elements" }
static any[] use = [
DescriptiveForm,
ScopedStyle{ src: '../styles/style.css' }
]
Body{
DescriptiveForm{}
}
}
Now, component selectors will work inside style.css
file:
html, body{
width: 100%;
height: 100%;
}
&DescriptiveForm{
height: 100%;
}
&DescriptiveForm &Col{
height: 100%;
}
We can also remove the Stylesheet
from the bundle file, as the server handles this file automatically now.
By using scoped styles inside components, you can create components that can be reused throghout your application, creating a more modular and maintainable code. By isolating the style of each component, you make sure that changes to that component do not inadvertently affect other parts of the application.