Welcome to the 4th post in the series! During the last three weeks, we've created a sheet with some nice functionality to it, but without any styling whatsoever. That changes this week! We're diving into the piece of coding a sheet that can either take just a few minutes for an extremely barebones sheet, or might be 80% of the time you spend working on your sheet for something with good aesthetics; at least that's how it is for me. So, let's start talking about CSS. As I mentioned in the first post in the series, we'll be coding the styling for our sheet using SCSS, which stands for syntactically awesome CSS. If you haven't already, I'd recommend giving the documentation on SCSS at least a cursory look. SCSS uses basic CSS syntax plus some extra features to allow us to write style code that is easy to read and easy to understand. We'll be touching on basic layout and some specific section layouts in today's post. Series Posts The Beginning Do the Pug Repeating Sections and sheetworkers Style and Layout Finding our Flair! Roll on! Laying it all out In the last post, we finally got to see our sheet in action in our sheet sandbox although it didn't look great. Our first step to fix that appearance problem is to get our SCSS files setup. Similarly to how we separated out our PUG files based on the section they were for, we're going to create separate SCSS files for the various aspects of our sheet. In the tutorial repo, you can see the folder structure I've gone with which looks like this: system.scss - Our master scss file mixins - Our folder for pug and SCSS mixin files SCSS - Our folder to hold all of our SCSS files. In addition to the files described below, we'll have scss files for each of our sections as well. _generalsetup.scss - This file will store all of our generic styling for the sheet. Styling for specific sections will be done in individual scss files to make the code easier to read. _mixinsandplaceholders.scss - This file will store all mixins and placeholders that have generic use. Placeholders are a feature of SCSS that allow you to easily apply the same CSS code to elements. They are similar to mixins, but don't accept any arguments. I've added my general starting SCSS code to the _mixinsandplaceholders.scss file. It's too much code to go over in detail in this blog post, but you'll find a fully commented version in the repository's folder for today's post. So, we'll start with the code for our general setup. Here's what my first pass at _generalsetup.scss looks like: @use '_mixinsandplaceholders';
html{
font-size:16px;
}
.ui-dialog{
@extend %font-vars;
.tab-content .charsheet{
select,
textarea,
input,
.uneditable-input,
label,
button{
all: initial;
}
}
}
.ui-dialog .tab-content .charsheet{
@extend %defaultStyles;
}
#main{
@extend %grid;
}
character{
@extend %grid;
gap:var(--grid-gap);
grid-template-columns:repeat(auto-fill,minmax(16rem,1fr));
} Still not great looking, but we have already done quite a bit of our styling: We've set the base font-size for our sheet (16px) in the html element. We'll use the rem element for most of our sizing needs from now on as it scales better for users of assistive technologies. We've cleared the default roll20 styles so that we won't be fighting against them as we do the rest of our styling. This will also make our sheet more future proof as it's less likely that a style change in Roll20's code will affect our sheet. We've given our inputs and buttons some default stylings. And, we've created an overall layout that is already responsive That's quite a lot for less than 30 lines of code. Ok, there's actually quite a lot of code that's hiding behind those extend lines. So, what exactly is each piece of this code doing? @use '_mixinsandplaceholders'; This line is just fetching our mixins and placeholders that we've coded already. We'll need to include this line in pretty much all of our SCSS files, and some of them will be using additional files as well. html{
font-size:16px;
} Note that this bit of code requires that we use the new CSS sanitizer that was part of the Character Sheet Enhancement rollout nearly a year ago; the legacy sanitizer won't accept modifications to the html element. Here, we're defining what we want our base font size to be. We'll use this font size for our basic spans, inputs, and textareas. 16px provides a nicely sized text for reading on a screen without being obnoxiously large. We'll scale text up or down from this as needed by using the rem unit. Since we've defined the font-size as 16px in the html element; 1rem = 16px, 0.5rem = 8px, 2rem = 32px, etc. The debate of whether to use px, em, or rem is one that everyone has an opinion on, and my method here is by no means the one and only way to do it. I'd highly recommend reading about the various benefits and pitfalls of each to decide what you want to do in your own project. .ui-dialog{
@extend %font-vars;
.tab-content .charsheet{
select,textarea,input,.uneditable-input,label,button{//moved to one line to save space
all: initial;
}
}
} And, we've got our first two pieces of SCSS specific syntax. In basic CSS we can't nest declarations within one another which means that you have to repeat a lot of selectors that get used frequently. In SCSS, we can nest our declarations inside of each other to avoid having to repeat declarations that get used a lot. And then we also have the @extend %font-vars; line. This is telling scss to look for something called a placeholder, find all the CSS declarations that invoke this line, and declare them all to have the same content. Placeholders are bits of code that exist only to be applied to other things. So this section of SCSS is equivalent to: .ui-dialog {
--topHeaderFont:Arial;//These three lines are the content of %font-vars
--midHeaderFont:Arial;
--contentFont:Arial;
}
.ui-dialog .tab-content .charsheet select,
.ui-dialog .tab-content .charsheet textarea,
.ui-dialog .tab-content .charsheet input,
.ui-dialog .tab-content .charsheet .uneditable-input,
.ui-dialog .tab-content .charsheet label,
.ui-dialog .tab-content .charsheet button {
all: initial;
} The SCSS is a lot easier to read. This bit of code does two things. We include our font variables so that we can easily use the fonts we'll eventually decide on for the sheet. It overrides all of the Roll20 specific styling on elements Number 2 there is actually a very big deal. Roll20 has quite a lot of default styling for elements on character sheets. From how wide a label should be to the background color of a button, these default styles can be helpful, but more often they are something that we have to manually override to get the style we want for our sheet. This bit of code removes the need to think about what styles are from Roll20 (and out of our control) and what is from our own code. However, because of CSS specificity, we'll need to use at least three classes to define all our css rules moving forward. Sidebar: What is CSS Specificity? We should also talk about how CSS works here. CSS stands for Cascading Style Sheets, and it operates on a logic model where there are two parts to determining which rule applies to an element when there are several rules that could apply to the element. That logic is: Which rule is the last rule (aka on the latest line number)? Incidentally, this is where the Cascading in CSS comes from. Which rule has the highest specificity? Now, the first part of that logic is pretty easy to understand. If you have: section{
background-color: blue;
}
section{
background-color: red;
} Your section is going to have a red background because that was the last thing defined. Our CSS for the character sheet will always come after the Roll20 specific styles just because of how the CSS for the character sheet is parsed. Logic piece 2 gets a little more complicated. In CSS a declaration has a defined specificity which is expressed as a series of numbers separated by dashes (e.g. 0-1-0 or 0-4-2). Each of those numbers represents how many of a certain type of CSS selector are being used. A broad strokes explanation of what each number means is to look at them as #IDs - #Classes - #Elements . Another piece of this to remember is that any number of IDs will trump any number of classes, which will in turn trump any number of elements. Fortunately, we don't have to calculate specificity by hand because there are any number of specificity calculators online; personally I like <a href="https://specificity.keegan.st/" rel="nofollow">https://specificity.keegan.st/</a> because it allows me to directly compare the specificity of two declarations. Looking at that summary of how CSS decides what rule to apply to a given element, you mighty think that we want to be as specific as possible with our CSS declarations. The actual best practice though is to only use as much specificity as you need. This is because being overspecific can make your code rigid and hard to change or override when you need to. For instance, if you use 5 classes to target all of our CSS, then if you need to override your default CSS on an element, you now need 6 classes or an ID. This can obviously spiral pretty quickly in a complex piece of code. Back to our actual code, here's the last few lines of our general css code again: .ui-dialog .tab-content .charsheet{
@extend %defaultStyles;
}
#main{
display:grid;
}
#character{
display:grid;
gap:var(--grid-gap);
grid-template-columns:repeat(auto-fill,minmax(16rem,1fr));
} That defaultStyles placeholder holds a lot of the code that we've applied. We're using it as a placeholder rather than just applying it directly to the character sheet so that we can make use of that same code when we eventually define our roll template style. Most of the rest of these lines are pretty simple; they're just setting our main container and our character article to grid display and setting up the gap in the grid on our article. If you aren't quite sure what all this CSS Grid talk is about, I'd highly recommend looking into some CSS grid tutorials. CSS Grid is a relatively recent addition to CSS that allows us to define a grid by which content should be laid out. CSS tricks has an excellent reference page for all the CSS grid properties and the Roll20 wiki has several useful links. And then there's the last line of our general CSS. This probably looks a little funky, but this single line (along with display:grid; ) is what makes our sheet responsive without needing to use media queries . We're defining the columns that should make up the grid, but unlike with a normal template column declaration, we aren't defining a number of columns. Instead, we're telling CSS to create as many columns as can be fit in the space, and that they should be no thinner than 16rem across, but should scale up to be at least an equal portion of the width of the container. This may seem like black magic (it sure did to me when I first encountered it), but CSS tricks has a great overview of how the technique works. Styling by sections Ok, so we've got our basic layout for the sheet as a whole done, but our individual sections still need some (or a lot of) work. We're going to want to do a few things: set up several of these sections to span more than one column Define a layout for each section Let's style our header section because it will make a good demo for both of these tasks. Here's how we're going to define our header section's layout: #character-header{
display:grid;
grid-template-columns: 14rem 1fr;
.character-details{
display:grid;
grid-template-columns:repeat(4,1fr);
gap:var(--half-gap);
:not(:is(.span-2,.span-all)){
justify-self:end;
}
}
} Which gives us this nice header area: To get this, I've also added some additional classes to our header section and its contents. You can see the addition of the span-2 and span-all classes in the code version for today. Aside from that, here's what the code is doing: display:grid;
grid-template-columns: 14rem 1fr; We're setting our section to display as grid, and defining two explicit columns in the grid that are going to be 14rem and all the remaining space wide respectively. The system logo will go in that first column because it's the first element in the container. The container for our actual inputs will go in the dynamic column because it's the second element. .character-details{
display:grid;
grid-template-columns:repeat(4,1fr);
gap:var(--half-gap);
:not(:is(.span-2,.span-all)){
justify-self:end;
}
} For our actual header inputs, we define 4 columns. The character name label has had .span-all added to it so that it will span across all the columns. The other label constructions, except for xp and level, have had .span-2 added to them so that they will span two columns. This leaves our xp and level to just span a single column, and we have these two elements justify themselves to the end of their columns. We're not done yet! Over the next week, I'll do similar layouts for each of the sections so that we'll wind up with a well laid out sheet that will be easy to read. Next week we'll start talking about how to add some flair to the sheet by looking at ways to recreate that parchment effect and activating our sheet navigation. Let me know if you're enjoying this series and if there's anything you'd like me to cover more in depth or differently. If you're enjoying this series of posts, support the Kurohyou Studios Patreon ! See the previous post | Check out the Repository | See the next post