@spritz-css/uno-preset v0.1.6
Spritz CSS
A light CSS framework that wants to make your life more fun.
What is Spritz
Spritz is a a cocktail of different ideas in how to create sharp user interfaces for websites and applications. Here’s are some decisions that set it apart.
- Spritz uses the excellent ideas in Every Layout to let you describe your interface using declarative classes like
row,stack, oritem-grid. These classes describe layout regardless of viewport sizes, removing the need for finnicky, viewport-based setups. - On top of that, Spritz sprinkles atomic classes like you would find in Tailwind CSS. You have classes like
gap-2,pad-4, oraspect-square. - Unlike Tailwind, there is no predefined list of sizes that you have to fight against. Sometimes a design looks best with a gap of 52px, so you can have your lucky
gap-13. Your 10 pixel padding is as easy aspad-2.5 - Spritz also supports Tailwind-style prefixes like
sm:orhover:, but is not opinionated about how these work. Go mobile-first or desktop-first. Mix and match. You can finally have yourbelow-sm:hidden. - There’s a nifty feature called semantic classes. Define a class like
pad--buttonand Spritz will set the padding tovar(--pad--button). Spritz isn’t here to tell you never to write CSS again, but we’ll try to make it easier for you. - Speaking of which, Spritz doesn’t have a custom configuration format for colors, sizes, or any theme properties. You’re reponsible for setting all of those up using CSS variables. That lets you reuse them in your own CSS.
- Spritz also chooses not to include a lot of things in its box.
- It avoids margins and favors paddings, gaps and nesting elements to control spacing.
- It does not have atomic classes for typographic attributes like font sizes or line heights. Instead, it encourages you to create cohesive typographic styles.
- It does not come with a lot of Tailwind bells and whistles like
group:,pad-[23px]. Use CSS when you need something fancy.
Installation
Coming soon.
Organization
Spritz splits its functionality into five parts, each of which relies on utility classes that you add to your elements.
- Layouts. These are utility classes that define how an element’s children should be laid out; this also includes utility classes for gaps and padding
- Position. Relative or static positioning; reordering items; position on the z axis.
- Dimensions. Width, height, aspect ratios, growing and shrinking
- Boxes. Setting borders, colors, padding(?)
- Typography. Use this for your own typographic classes and to customize those as needed.
Writing clean classes
To make things easier for your readers it is recommended (but not required) that you use vertical bars to separate classes based on the five parts above. Any of your own classes (we call them selectors) should go first.
<div
class="notice | row gap-8 | fixed block-start-4 inline-end-4 | min-block-100 | bg-white border-gray-300 shadow--sm | type-notice"
>
...
</div>If lines get too long, you can also split classes up on different lines:
<div
class="
notice
row gap-8
fixed block-start-4 inline-end-4
min-block-100
bg-white border-gray-300 shadow--sm
type-notice"
>
...
</div>Again, none of this is required to use Spritz, it’s just a suggestion to make your code easier to read.
Media query modifiers
Out of the box, Spritz lets you write classes like print:hidden which tell it to generate CSS like this:
@media print {
.print\:hidden {
display: none;
}
}The following media query prefixes are supported out of the box:
printwraps classes in@media printlightwraps them in@media (prefers-color-scheme: light)darkwraps them in@media (prefers-color-scheme: dark)
The powerful part though, is being able to write your own media queries. In your uno.config.js you can set it up like this.
import { defineConfig } from "unocss";
import presetSpritz from "@spritz-css/uno-preset";
export default defineConfig({
theme: {
mediaQueries: {
sm: "(min-width: 480px)",
},
},
});This is similar to what you can do with Tailwind, but with Spritz media queries you have a lot more power.
A common thing to do is writing media queries that target viewports smaller than a certain size:
"below-sm": "(max-width: 480px)",With this you can switch from Tailwilnd code like <div class="hidden sm:block" /> to <div class="below-sm:hidden">.
Postfix modifiers
You can write classes like hover:weight-700 which Spritz convert into this:
.hover\:weight-700:hover {
font-weight: 700;
}The following modifiers are supported:
hoverwill add:hoverto the classfocuswill add:focus-visiblewhich, unlike:focus, is what you want for customizing appearanceactivewill add:activeoddwill add:nth-of-type(odd)which is more robust than:nth-child.evenwill add:nth-of-type(even)firstwill add:first-of-typelastwill add:last-of-typenot-firstwill add:not(:first-of-type)not-lastwill add:not(:last-of-type)
Support for adding custom modifiers will be added in a future release.
Utility classes
Like Tailwind, Spritz lets you write utility classes with numbers describing sizes. For instance, you can write pad-4 and Spritz will generate a class with padding: 16px. But unlike Tailwind there is no predetermined catalogue of values. You can write pad-23 and Spritz will happily generate a class with padding: 92px. It’s your party!
You can also use media query prefixes to tweak sizes based on the browser’s viewport like pad-4 lg:pad-6. This is great for prototyping and is good enough for 95% of all your designs.
But every now and then you end up with classes like pad-4 sm:pad-6 md:pad-8 lg:pad-10 getting repeated in five different and unrelated components.`
Spritz lets you write a class like pad--box which will generate padding: var(--pad--box); (notice the two dashes which are there to avoid naming collisions with your existing CSS code). Now all that’s left to do is define that CSS variable somewhere.
Even better, due to the magic of CSS variables, you can override what --pad--box means inside one individual element by changing that variable only within that one element.
Inline and block
Spritz doesn’t use terms like top or bottom, left or right, x or y, column or row. Instead it relies on the words inline and block as used in CSS logical properties. If you’re trying to remember which is which, a good rule of thumb is to think of how spans flow using display: inline and divs flow using display: block.
Spritz also doesn’t have numeric utility classes for individual directions. There’s nothing that will set padding-inline-start or padding-left. Instead we rely on pad-inline- classes to set inline padding in both directions.
In the 1% of cases where this doesn’t work for you, you can use logical properties instead. Add a class like pad-inline--custom and set --pad-inline--custom: 16px 0 which is equivalent to setting pad-inline-start.
No margins for spacing.
Spritz does not have classes like mt-4 ml-2 or mx-8 which are used heavily in Tailwind to control spacing between elements. Instead, we rely on Flexbox and CSS grid and use gaps. When we find ourselves needing to customize the spacing between two elements, we wrap them.
This can make sense because needing to customize the spacing can be a sign that there is a certain relationship between these two elements, that they “belong together” somehow.
Layouts
Layouts describe how an element’s children should be displayed on the page.
They describe how children should be laid out across all viewport sizes. They are based on the book Every Layout by Heydon Pickering and Andy Bell. Unless otherwise stated, they’re implemented using Flexbox.
The following layouts are supported (visual examples to be added soon)
stackwill stack children on the block axis regardless of screen size. By default children are justified to the start and do not stretch to fill the stack’s width.- adding a
split-firstclass to a stack that has, say, a minimum height of100vhwill leave split off the first element from all the others. - adding a
split-lastclass to a stack will split off the last element.
- adding a
rowwill place children in a single row which will not wrap. By default children are center aligned.- rows also support the
split-firstandsplit-lastclasses; they work the same but on the other axis.
- rows also support the
center-contentswill place children in the vertical and horizontal center of the current element. It’s implemented using Flexbox.clusterwill place things in a row but wrap to multiple rows if they don’t all fit.with-sidebaris useful for creating layouts where one element is a fixed size and the other one grows to take up all remaining space. It its child has a class ofsidebarit will be fixed in width, otherwise it grow.switcherwill put things on a row when the parent element is wider than a certain size. You specify the threshold below which children should stack using athreshold-class. For example, addingswitcher threshold-120to the classes will stack children when the element is narrower than 480px and put them in a row otherwise.coverstacks three children such that the first and last one take up a fixed width and the center one expands to fit the available vertical space. It’s implemented using CSS grid to ensure equal sizes of the first and last children.item-gridwill place children in a grid of equally sized boxes which wrap. It is implemented using CSS Grid. That means that unlikerow, there is no risk that an orphaned element will take up an entire row. You control the size of the children with anitem-width-class likeitem-grid item-width-40which would create a grid of items 160 pixels wide.frameis a parent for images or videos. When smaller, they will be centered within the frame but they will never exceed its bounds. By default frames have an aspect ratio of16/9but you can customize this withaspect--classes.superimposeputs a child with thesuperimposedclass on top of all other children, in the center of the parent. This can be useful for certain kinds of modals, log in prompts for paywalls and other questionable activities.with-iconTODO ensure this is implemented correctly and document it.dividercan be used to set up a visual line between elements in a stack or row. TODO make this consistent and document it better.boundaryindicates that children using theboundclass will be positioned relative to this parent using classes likeblock-start-0 inline-start-0. These are semantic names forposition: relativeandposition: absolutevisually-hiddenhides items but allows screen readers to read them. Theexcept-on-focuswill show the item when the user has navigated inside it using the keyboard which is handy for skip navigation links.
One thing to note about these layout classes is they do not work with prefixes. That’s because they are meant to work on all viewport sizes. However, you can always hide them like this:
<div class="row below-sm:hidden" />Layout options
Separate from the layout classes themselves, there’s a set of attributes that let you tweak the layouts to fit your needs.
Gap classes let you specify gaps between the children of a class:
- Numeric classes like
gap-4or semantic ones likegap--sectionlet you specify gaps in both block and inline directions. - Classes like
gap-inline-4orgap-inline--sectionspecify gaps in the inline direction only. - Classes like
gap-block-4orgap-block--sectionspecify gaps in the block direction only.
Padding also supports both numeric and semantic clases. For example:
pad-8would setpadding: 32pxpad-inline-8would setpadding-inline: 32pxpad-block-8would setpadding-block: 32pxpad--buttonwould setpadding: var(--pad--button)pad-inline--buttonwould setpadding-inline: var(--pad-inline--button)pad-block--buttonwould setpadding-block: var(--pad-block--button)
Alignment classes specify how items are laid out on the block axis. It works by setting the align-items property.
align-startaligns all children to the start of the blockalign-endaligns all children to the end of the blockalign-centeraligns all children to the center of the blockalign-stretchstretches all children so they take up the entire blockalign-baselinealigns all children to the text baseline.
Justify classes specify how items are laid out on the inline axis. It works by setting the justify-content property.
justify-startjustifies all children to the inline start of the elementjustify-endjustifies all children to the inline endjustify-centerjustifies all children to the inline center.justify-betweensets thespace-betweenvalue which means there’s equal spacing between children but they sit flush with the edges.justify-aroundsets thespace-aroundvalue which means there’s equal spacing between children and half of that spacing is used at the edges.justify-evenlysets thespace-evenlyvalue which means that there’s equal spacing between the elements and the same spacing is used at the edges.
Hiding is acomplished with the hidden class which sets display: none.
Isolation with the isolate class creates a context in which all children’s z-index properties are evaluated against each other. If, like most of us human beings, you are confused by how z-index works, this article by Josh Comeau is a great resource.
Overflow classes control what happens when an element’s children don’t all fit within the element.
overflow-visiblelets the children break outside the boundsoverflow-hiddenhides the parts outside the boundsoverflow-scrollalways creates scrollbars for the elementoverflow-autoonly creates scrollbars when the content overflows.
Dimensions
Dimension classes refer to the size of an element, either on its own or in relation to its siblings.
Inline size classes refer to the inline size of an element. They support both numeric and semantic classes.
- Classes like
min-inline-4ormin-inline--buttoncontrol the minimum inline size of an element. - Classes like
max-inline-200ormax-inline--maincontrol the maximum inline size of an element - Classes like
inline-100orinline--illustrationcontrol the size of the element without letting it grow beyond that point.
One thing to note is numeric classes will constrain the size of an element so that it can’t be wider than the parent element. That is, a class like min-inline-40 will generate min-inline-size: min(160px, 100%)
This prevents creating components that cause your page to scroll horizontally on mobile devices.
Block size classes refer to the inline size of an element. They support both numeric and semantic classes.
- Classes like
min-block-4ormin-block--buttoncontrol the minimum block size of an element. - Classes like
max-block-200ormax-block--maincontrol the maximum block size of an element - Classes like
block-100orblock--illustrationcontrol the size of the element without letting it grow beyond that point.
Aspect ratios can be set using predefined and semantic classes:
aspect-squaresets a square aspect ratioaspect-videosets a 16:9 aspect ratioaspect--portraitwould set `aspect-ratio: var(--aspect--portrait);
Since a lot of layout classes use Flexbox under the hood, Spritz defines some classes to help with Flexbox-based layouts. For example:
basis-10would setflex-basis: 40pxgrow-5would setflex-grow: 5grow-maxwould setflex-grow: 9999shrink-0would setflex-shrink: 0
You can also control the way the dimensions of a box are calculated based on the selected box model:
box-contentwill setbox-sizing: content-boxbox-borderwill setbox-sizing: border-box
Position
The following classes describe how an element should be positioned:
boundmeans the element will be positioned relative to a parentboundary– it’s a semantic class that setsposition: absolutestaticis the default static positioningrelativesetsposition: relativeplacing an element relative to where it would have normally been placedfixedsetsposition: fixedstickysetsposition: sticky
These must be used together with classes which describe the actual coordinates. Spritz doesn’t use classes like top, bottom, left or right. It opts for logical properties instead. For example:
block-start-2would setinset-block-start: 8pxblock-end-2would setinset-block-end: 8pxinline-start-2would setinset-inline-start: 8pxinline-end-2would setinset-inline-end: 8pxblock-start--tooltipwould setinset-block-start: var(--block-start--tooltip)block-end--tooltipwould setinset-block-end: var(--block-end--tooltip)inline-start--tooltipwould setinset-inline-start: var(--inline-start--tooltip)inline-end--tooltipwould setinset-inline-end: var(--inline-end--tooltip)
Z-index is set with z- classes. For example z-10 will set z-index: 10
Order classes determine how elements will be visually ordered:
order-2will setorder: 2order-firstwill make the element first by settingorder: -999; you should only use this once within a set of siblingsorder-lastwill make the element last by settingorder: 999; you should only use this once within a set of siblings
Boxes
Box classes define the visual boxes in which content is displayed. This can apply to buttons, cards, modals or anything with a visible background or borders.
Foreground and background colors are always semantic but they rely on a --color- prefix. For example:
fg-gray-500will setcolor: var(--color-gray-500)bg-gray-500will setbackground-color: var(--color-gray-500)
Opacity is set numerically, as a percentage value. So opacity-99 would set opacity: 0.99
Blur can be set with to pixel value. The greater the value, the blurrier the result. So blur-1 will set backdrop-filter: blur(4px).
Borders are set with the same color semantic classes used for foreground and background colors. But they can then be customized:
border-gray-500will setborder: solid 1px var(--color-gray-500)border-2.5will setborder-width: 2.5px(this works for anything purely numerical with no letters afterborder-)border-nonewill hide the borderborder-solidwill set a solid borderborder-dottedwill set a dotted borderborder-dashedwill set a dashed borderborder-transparentwill set a transparent border (this can be handy when borders are shown on hover)
The same options are available for outlines by changing border- with outline-
Shadows are set in a similar way to color. Adding a class of shadow--sm will set box-shadow: var(--shadow--sm)
Typography
Spritz does not include a lot of the classes you will find in Tailwind. This is because those classes encouraging thinking of typography in ways that lead to unsatisfying designs. Rather than attempt to poke at pre-determined size and line height classes until something “looks right” you should create your type classes that semantically describe how type should be used. For example:
.type-examples-caption {
font-weight: 430;
font-size: 0.875rem;
line-height: 1.375rem;
font-feature-settings: "ss02" on, "ss03" on;
@media (min-width: 30rem) {
font-size: 1rem;
line-height: 1.375rem;
}
@media (min-width: 60rem) {
font-weight: 450;
font-size: 1.125rem;
line-height: 1.3125rem;
}
}Notice how in this example:
- Sizes are set in
remunits as you should - The font weight is slightly adjusted based on the viewport size. for a variable font in this case
- OpenType font features are set.
By doing all of this in one go, users of this class don’t have to worry about getting 10 things right in tandem.
That said, there are a few knobs you can tweak on the go.
weight-700will setfont-weight: 700; useful for the occasional active link or hover variantitalicwill set the font style to italicnon-italicwill set the font style to normalunderlinewill decorate the text with an underlineline-throughwill strike the text throughno-linewill not decorate with any linedecoration-1.5will set a1.5pxlinedecoration-from-fontwill use font data to decide how thick the line should be
There are a few helpers related to wrapping and truncation:
no-wrapwill force text not to wrap (should be avoided, but handy when Flexbox is wrapping something against your will)truncatewill truncate text so it doesn’t wrap; best avoided if you canline-clamp-2will only display the first two lines of your text, truncating afterwards
You can also control the justification of the text:
text-startwill justify to the start (which means left for LTR languages and vice-versa)text-endwill justify to the endtext-centerwill center the texttext-justifywill justify along both edges
There are a few options for controlling how list decorations are displayed:
list-insidewill setlist-style-position: insidelist-insidewill setlist-style-position: outsidelist-nonewill setlist-style-type: nonelist-discwill setlist-style-type: disc- more to come soon