In this chapter, we will look at styling the classroom application we created previously. Normally in html and css you would start adding classes to the html elements, and then use those classes in index.css
file to select and style each element. However, Live Elements has a faster way to do these things, using scoped styles.
Scoped styles basically mean styles that are limited to a scope. In case of Live Elements, that scope is a component. So in Live Elements, a component can have styles that are applied only to that component, and they won't interfere with styles from other components accross the application. Creating a component with scoped styles assigned will also apply the styles for that component. There are already a few of these components implemented in the live-web-view
package.
PrimaryButton
is a styled button found in live-web-view
package, in live-web-view.button
module. We can try it out in the class room example:
import live-web.dom
import live-web-view.button // Import the module
import live-elements-web-server.view
import live-elements-web-server.style
component ClassroomData{
string label: ''
number attendeeCount: 0
}
component Classroom < Li{
id: classRoom
ClassroomData data: ClassroomData{}
T{ text: 'Classroom ' + classRoom.data.label }
PrimaryButton{ // Switch from Button to PrimaryButton
on click: () => {
if ( classRoom.data.attendeeCount > 0 )
classRoom.data.attendeeCount--
}
T`-`
}
Span{ T{ text: classRoom.data.attendeeCount } }
PrimaryButton{ // Switch from Button to PrimaryButton
on click: () => { classRoom.data.attendeeCount++ }
T`+`
}
}
component Index < PageView{
static any[] use = [ ScopedStyle{ src: './index.css'} ]
Ul{
Classroom{ data: ClassroomData{ label: 'A' } }
Classroom{ data: ClassroomData{ label: 'B' } }
Classroom{ data: ClassroomData{ label: 'C' } }
}
}
The example works like previously, but the button looks the same. Even though we are using PrimaryButton
the server running our example isn't aware of that, and so it cannot populate the stylesheet accordingly. This is where the use
property comes along. Besides letting the application know we are using the ScopedStyle{ src: './index.css' }
, we will also need to let it know we are using the PrimaryButton
:
static any[] use = [
PrimaryButton,
ScopedStyle{ src: './index.css'}
]
index.css
should also be added at the end in order to be able to override any styles from the PrimaryButton
.
So the entire implementation will be:
import live-web.dom
import live-web-view.button // Import the module
import live-elements-web-server.view
import live-elements-web-server.style
component ClassroomData{
string label: ''
number attendeeCount: 0
}
component Classroom < Li{
id: classRoom
ClassroomData data: ClassroomData{}
T{ text: 'Classroom ' + classRoom.data.label }
PrimaryButton{ // Switch from Button to PrimaryButton
on click: () => {
if ( classRoom.data.attendeeCount > 0 )
classRoom.data.attendeeCount--
}
T`-`
}
Span{ T{ text: classRoom.data.attendeeCount } }
PrimaryButton{ // Switch from Button to PrimaryButton
on click: () => { classRoom.data.attendeeCount++ }
T`+`
}
}
component Index < PageView{
static any[] use = [
PrimaryButton, // Use PrimaryButton
ScopedStyle{ src: './index.css'}
]
Ul{
Classroom{ data: ClassroomData{ label: 'A' } }
Classroom{ data: ClassroomData{ label: 'B' } }
Classroom{ data: ClassroomData{ label: 'C' } }
}
}
Now we can see the actual change take effect. Let's add some more components.
CenterLayout
and UlV
CenterLayout
is a component that will center all it's children. It's available in live-web-view.layout
:
import live-web.dom
import live-web-view.button
import live-web-view.layout // Import live-web-view.layout
import live-elements-web-server.view
import live-elements-web-server.style
component ClassroomData{
string label: ''
number attendeeCount: 0
}
component Classroom < Li{
id: classRoom
ClassroomData data: ClassroomData{}
T{ text: 'Classroom ' + classRoom.data.label }
PrimaryButton{
on click: () => {
if ( classRoom.data.attendeeCount > 0 )
classRoom.data.attendeeCount--
}
T`-`
}
Span{ T{ text: classRoom.data.attendeeCount } }
PrimaryButton{
on click: () => { classRoom.data.attendeeCount++ }
T`+`
}
}
component Index < PageView{
static any[] use = [
PrimaryButton,
CenterLayout, // Use CenterLayout
ScopedStyle{ src: './index.css'}
]
CenterLayout{ // Move Ul in CenterLayout
Ul{
Classroom{ data: ClassroomData{ label: 'A' } }
Classroom{ data: ClassroomData{ label: 'B' } }
Classroom{ data: ClassroomData{ label: 'C' } }
}
}
}
UlV
is a styled Ul
. The letter 'V' comes from vertical, so it's a vertical list, there's also an UlH
for horizontal lists:
import live-web.dom
import live-web-view.button
import live-web-view.layout
import live-web-view.content
import live-elements-web-server.view
import live-elements-web-server.style
component ClassroomData{
string label: ''
number attendeeCount: 0
}
component Classroom < Li{
id: classRoom
ClassroomData data: ClassroomData{}
T{ text: 'Classroom ' + classRoom.data.label }
PrimaryButton{
on click: () => {
if ( classRoom.data.attendeeCount > 0 )
classRoom.data.attendeeCount--
}
T`-`
}
Span{ T{ text: classRoom.data.attendeeCount } }
PrimaryButton{
on click: () => { classRoom.data.attendeeCount++ }
T`+`
}
}
component Index < PageView{
static any[] use = [
PrimaryButton,
CenterLayout,
UlV,
ScopedStyle{ src: './index.css'}
]
CenterLayout{
UlV{
Classroom{ data: ClassroomData{ label: 'A' } }
Classroom{ data: ClassroomData{ label: 'B' } }
Classroom{ data: ClassroomData{ label: 'C' } }
}
}
}
The components we are using are now styled, but we should still do some updates so the design blends in better throughout the page. We should modify the margin for the PrimaryButton
, so the text has more room around it. One way to do that is to add a class to the primary button, something like primary-button
, and then select it in css with .primary-button { ... }
. Another way is to use a specific selector live elements provides in css for scoped style components:
&PrimaryButton{ margin: 0 5px; }
&PrimaryButton
will select all PrimaryButton
components in our webpage. This type of selector, '&ComponentName', can be used for any component that's being used by the component using the current stylesheet. Specifically, the Index
component in Index.lv
uses PrimaryButton
, CenterLayout
, UlV
, and it also uses index.css
:
static any[] use = [
PrimaryButton,
CenterLayout,
UlV,
ScopedStyle{ src: './index.css'}
]
This means index.css
has access to all these 3 components, plus any nested component inside those components. For example, if UlV
uses IconButton
, then &IconButton
selector would be available in index.css
as a selector.
Let's wrap up the styling in index.css
:
&PrimaryButton{ margin: 0 5px; }
&UlV{ font-size: 1.3rem; }
&UlV li span{ font-weight: bold; }
There are 2 more things to note on scoped styles, one is that most components with scoped styles reserve the classes
property internally, and expose another property extraClasses
instead. For example, to add an html class to PrimaryButton
we should use the extraClasses
property:
PrimaryButton{
extraClasses: ['green-button']
}
The second is when using additional selectors beside the component name, we need to append the &
symbol after the component name:
&PrimaryButton&.green-button { color: green; }
Since the playground currently doesn't support creating new files, and since a scoped style component needs a separate style file, we won't be able to create a component with scoped styles here, but the scoped styles tutorial will guide you through creating a component with scoped styles in a development environment.
We will overview how to create such a component instead. We can look at an already implemented component. Let's check out the source for CenterLayout
:
CenterLayout.lv
:
import live-web.dom
import live-web
import live-elements-web-server.style
component CenterLayout < Div{
static any[] use = [
ScopedStyle{ src: './style/centerlayout.css' process: '../style/CSSProcessor.lv' },
ScopedStyle{ src: '../style/global.css' process: '../style/CSSProcessor.lv' }
]
Array extraClasses: []
classes: this.extraClasses.concat([ ScopedStyle.className(CenterLayout) ])
}
and centerlayout.css
:
&CenterLayout {
@apply flex flex-col justify-center items-center w-full h-full;
}
In CenterLayout.lv
we can see CenterLayout
component is extending the Div
(component CenterLayout < Div
), so it will generate a <div>
tag in html. Next, it's using the centerlayout.css
file and a global.css
file. There's also a process
property for both styles assigned to CSSProcessor.lv
. The process
property can specify a component that will process the file before it is delivered to the client. This particular CSSProcessor
is using tailwind css to do the processing, it's why the css file is using the @apply
directive from tailwind css:
@apply flex flex-col justify-center items-center w-full h-full;
global.css
is simply to initialize the global tailwind styles, like @tailwind base;
and @tailwind components;
since we are using tailwind css processor. Otherwise, the process
property allows you to specify other css processors, or even create your own custom css processor implementation if you want.
We haven't really mentioned how are styles scoped to a component. Basically, in every style file used by CenterLayout
, any selector that doesn't start with &CenterLayout
will have the &CenterLayout
selector prepended automatically:
&CenterLayout {
@apply flex flex-col justify-center items-center w-full h-full;
}
/*
This will automatically become '&CenterLayout span' since it's part of CenterLayout
*/
span{
font-weight: bold;
}
In this chapter, we've looked at:
PrimaryButton
, CenterLayout
, UlV
.use
property and how it links to components with scoped styles and stylesheets.&
selector in cssprocess
property and how custom processors can be used for stylesheets.The next chapter will go through a list of examples with scoped style components available in the live-web-view
package.