While we looked under the hood of the blog in the first part of this series, we will focus on what meets the eye in this post. This is a tech-blog, there is not a lot of media content and funky pictures. Still, I don't want it to look boring and add some eye candy here and there. I don't want to distract the reader from the content either. What you see is what I consider my middle ground.
Color choice
Popular UI libraries like material Ui typically ask you to provide only a primary and a secondary color (sometimes tertiary), which makes sure that the design is calm. The main color here is green, as you may have noted. Why? Idk. I saw an ad somewhere which claimed you would appear trustworthy when you wear green. That's much likely bogus. Anyway, how do you get a fitting secondary color? I recommend visiting https://color.adobe.com/. The website is free and you can generate nice color palettes there. I chose the split-complementary option. The colors have good contrast but they are not perfect complements. As the middle color, I picked a dark red (see underlines on headers), so I get a light and a darker shade of green. It also outputs a more yellowish type of green, which I save as tertiary color, which is used sparsely, eg. for in-text code snippets.
Tailwind
Css can be a pain. A real pain. There are a ton of rules, eg. which properties cascade down and which don't, how specificity is calculated, or how a div behaves differently than a span. You can change everything, all it takes is time. I once fought against a
property from a SharePoint site template. I just wanted to get rid of it, but it always seemed like the guys from Microsoft did a better job in making their min-width
more specific. Took me half a day to figure it out. That is ok when you are learning. But obviously, being productive is something different.
min-width
Maybe it's a mentality thing. Some people study the package instructions first, I prefer to throw the toast into the toaster and see what's happening. Newer tools make that much easier. With Sveltes style method you write your styles close to the elements, so you don't need to search for them in extra css files. And the definitions are scoped so that specificity is not a problem.
So why do I choose Tailwind? One reason is refactoring. Now, I like the color of the blog, but one day I will be tired of the green. Hard-coding the colors in every component is not smart. I could work around it with css variables, but what if I want to increase all the margins someday? Or something else? With Tailwind you apply any classes directly to the elements, similar to inline styling. At the same time, Tailwind can be configured in its config file, so refactoring is easy. Here is a part of my tailwind.config.js:
/// tailwind.config.js module.exports = { theme: { colors:{ primary:"#B50033", secondary:{ DEFAULT: '#0A694B', light:"#09B580" }, tertiary:{ DEFAULT:'#3E690A', light:"#67B509" }, background: "#E1EBE7", codebackground:"#2e3440", code:"#2e3440", }, ... } }
I just add colors, but here you can change nearly everything Tailwind related on you page. Link to Docs.
Another reason is that Tailwind makes responsive design and things like
and :hover
easy. Ok, it is not hard with plain css, but with Tailwind, there is really not much you need to remember. Also media queries are dead simple. Just put the size, eg. :active
or sm:
, in front of nearly every class, and your done with it.
lg:
Last but not least, since class names are only strings in JavaScript, they can be passed down as props.
The 'cost' of Tailwind is that you need to learn the syntax and that the build process becomes more complicated. The syntax is on point, properties that you need often are kept short, eg.
is shortened to margin
, m-1
etc., if you only want the margin on the left side it is m-2
, only horizontal (left and right) is ml-1
and so on. Less likely properties like mx-1
get meaningful names, in this case, the class is called position: absolute
. I found it easy to drive into the syntax and you can look up all classes on the Tailwind website.
absolute
The build process is a little more inconvenient, though. The normal Tailwind workflow is that Tailwind creates a big css file with all classes you might use. Then, typically for the production build, the file is purged via post css, which simply means that a process walks through your code and only keeps the css classes that you use. You may remember, I use elderjs. It already has a little complicated rollup process, so I decided to apply the workflow manually. In package.json I add the following script:
/// package.json ... "twp": "NODE_ENV=production npx tailwindcss-cli@latest build ./scripts/base.css -o ./scripts/tailwind_p.css -c ./tailwind.config.js && postcss ./scripts/tailwind_p.css > scripts/tailwind.min.css && node ./scripts/purgeTW.js", ...
In the first command, the file
is created based on tailwind_p.css
and base.css
. The second command (tailwind.config.js
) purges the css file into postcss...
. As the last step, a small node script is executed. The script checks, if the file changed from the previous run and if so it copies the file to the assets folder. It renames the file with an up-counted version number at the end, to inform browsers about the change.
tailwind.min.css
Sketchy style: Roughjs
Some time ago I wrote Svelte specific typescript declaration files. Wired Elements is a collection of sketchy web-components. These components look great, but they are not very customizable and can't be animated. For a blog, I don't need a lot of different components, so I borrowed the idea and made my own components: add an overlay svg or canvas to the html element and draw shapes with Roughjs.
>How to make an overlay svg/canvas:
Set the styleand sufficient padding to the element, which you want to overlay.
position:relative
Add the svg or canvas as a child and set the following styles
position:absolute; top:0; left:0; right:0; bottom:0;
For cards, which are used for code snippets and blockquotes I use the canvas version of Roughjs since a lot of lines are drawn. With svg, the performance on mobile phones was not so good. It is just a square with the hachure option and no border. All other elements use svg. This has the advantage that it can easily be animated with Sveltes draw transition. Let's take a look at a box element:
<script> import { onMount } from 'svelte'; import rough from 'roughjs/bundled/rough.esm'; import { draw } from 'svelte/transition'; export let paths = []; export let delay=0; export let color="text-secondary"; let svg; let w = 1, h = 1; onMount(() => { if (!document) { return; } const rc = rough.svg(svg); const S = svg.closest('svg'); w = S.clientWidth; h = S.clientHeight; const cornersS = [ [0, 3], [w - 3, 0], [w, h - 3], [3, h], ]; const cornersE = [ [w, 3], [w - 3, h], [0, h - 3], [3, 0], ]; for (let j = 0; j < 2; j++) { for (let i = 0; i < 4; i++) { const c1 = cornersS[i]; const c2 = cornersE[i]; const node = rc.line(c1[0], c1[1], c2[0], c2[1], { roughness: 1.5, stroke: 'red', }); paths.push(node.children[0].getAttribute('d')); } } paths = paths; }); </script> <svg preserveAspectRatio="none" viewbox={'0 0 ' + w + ' ' + h} width="100%" height="100%" overflow="visible" bind:this={svg}> {#if paths.length > 0} {#each paths as path, i} <path class={"stroke-current "+color} transition:draw={{!span!}} style="fill:none;stroke-width:1.2" d={path} /> {/each} {/if} </svg>
What happens here? When the component is loaded, an empty svg element is added. Then, the
callback fires. Roughjs needs a reference to an svg element. It creates eight rough line path elements from which only the generated onMount
-attribute is used. The paths are then added with the Svelte d
syntax. They are animated with the draw transition. The draw parameters are chosen so that the duration gets smaller with every path, which means drawing speed increases. Delay also increases, which makes it look like they are drawn after each other.
There is also an exported delay prop, which is used on the home page. On the home page, the topics are listed in boxes and it looks weird when all animations happen in sync.
Another cool thing to note is that elderjs by default uses an intersection observer when hydrating components, which means that the animation is delayed until the user scrolls close to this component.
{#each}
Summary
The style of a blog is like your clothes: it should fit you, be practical and look good. Colors have a big impact. Picking harmonic colors is not so easy. I found the Adobe color page helpful.
Tailwind fits very well into Sveltes 'write less'- philosophy. There are pros and cons, but for me, the positive sides predominate. For me, the biggest plusses are that you change the styles directly at the components you still can refactor everything in a central location.
With Roughjs you can add a nice random touch to your page. The possibilities are endless, and it is fun to design these pieces. I only show the code for few components, but the process is similar for all of them, eg. the social media buttons at the top of the page or the coffee logo at the bottom.
Share this article on twitter - Vote on next topics - Support me
Next up: How this blog is made iii - PHP backend and legal considerations. Coming soon
Previous: How this blog is made - Basic Structure, Elder and Markdown
This might also be interesting for you:
- React Hooks in Svelte
- tree-shake-css - project page
- Sveltes tick is my new best friend - porting react-textfit to Svelte
- How to merge cells with svelte-window
- Porting React to Svelte - From react-window 1:1 to svelte-window
- Poll: Which React library would you love to see in Svelte?
- Porting React components to Svelte - It might be easier than you think
- Why are component libraries so complicated with React compared to Svelte?
- Svelte and Typescript
- Showcase: The Descent Ripple Effect or The React Descent Ripple Effect