moss

a fuzzy tuft of green moss
magical organic stylesheets 🌿

CSS framework

npm i -D @ryanatkn/moss

introduction #

Moss is a CSS framework and design system that can be used with any website and JS framework. It's built around style variables, which are CSS custom properties and design tokens with additional capabilities and conventions.

About Moss:

  • plain CSS
  • zero dependencies
  • exports one main stylesheet that can be optimized by other tools
  • also exports the underlying CSS partials, helpers, types, and data
  • uses its own concept of style variables, a specialization of CSS custom properties and design tokens
    • variables are the main source of truth
    • each variable provides values to light and/or dark mode
    • includes optional utility and component classes that use the variables
  • themes are groups of variables
  • dark mode is a first-class concept in the system, not a theme, instead each theme can support light and/or dark color-schemes
  • includes a CSS reset that styles HTML tags directly with the variables
  • is agnostic to JS frameworks, for example usage see Themed in my Svelte UI library Fuz

The stylesheets:

Moss is being made to support Zzz and my other projects that focus on end-users, so it'll grow relatively slowly as I encounter more usecases. It's hobby-ready but expect a lot of breaking changes. Feel free to take the code and ideas for your own purposes.

In the docs, you'll see I'm writing asides using "⚠️" with open questions and other discussion of uncertainties. Your input is appreciated in the Discord, the GitHub issues, or other social media, on these and other topics, see contributing.md for more 🌿

themes #

Moss supports both the browser's color-scheme and custom themes based on variables, which use CSS custom properties .

Moss works with any JS framework, but it provides only stylesheets, not integrations. This website uses my Svelte UI library Fuz to provide the UI below to control the Moss color scheme and themes.

Color scheme
#

Moss supports color-scheme with dark and light modes. To apply dark mode manually, add the dark class to the root html element.

The Fuz integration detects the default with prefers-color-scheme , and users can also set it directly with a component like this one:

The builtin themes support both dark and light color schemes. Custom themes may support one or both color schemes.

Builtin themes
#

A theme is a simple JSON collection of variables that can be transformed into CSS that set custom properties. Each variable can have values for light and/or dark color schemes. In other words, "dark" isn't a theme, it's a mode that any theme can implement.

These docs are a work in progress, for now see 🗎 @ryanatkn/moss/theme.ts and 🗎 @ryanatkn/moss/themes.ts.

  • variables #

    Style variables, or just "variables" in Moss, are CSS custom properties that can be grouped into a theme. Each variable can have values for light and/or dark color-schemes . They're design tokens with an API.

    The goal of the variables system is to provide runtime theming that's efficient and ergnomic for both developers and end-users. Variables can be composed in multiple ways:

    • by CSS classes, both utility and component
    • by other variables, both in calculations and to add useful semantics (e.g. button_fill_hover defaults to fg_2 but can be themed independently)
    • in JS like the Svelte components in Fuz

    Variables also provide an interface that's generally secure for user-generated content, if you're into that kind of thing.

    The result is a flexible system that aligns with modern CSS to deliver high-capability UX and DX with minimal overhead.

    export interface Theme {
    	name: string;
    	variables: Style_Variable[];
    }
    
    export interface Style_Variable {
    	name: string;
    	light?: string;
    	dark?: string;
    	summary?: string;
    }

    All 467 style variables
    #

    classes #

    Optional CSS classes
    #

    Moss has three optional CSS files with different kinds of classes:

    <!-- +layout.svelte -->
    <script>
    	import '@ryanatkn/moss/style.css';
    	import '@ryanatkn/moss/theme.css'; // or bring your own
    	// or import individual parts of `@ryanatkn/moss/style.css`:
    	import '@ryanatkn/moss/style_reset.css'; // required
    	import '@ryanatkn/moss/style_utilities.css'; // optional
    	import '@ryanatkn/moss/style_components.css'; // optional
    	import '@ryanatkn/moss/style_animations.css'; // optional
    	// ...
    </script>

    Utility classes use plain CSS for convenient HTML authoring. Many are based on Moss style variables.

    Component classes are semantic groupings of styles.

    Utility classes
    #

    • .relative|absolute|fixed|sticky|static
    • .overflow_auto|hidden|scroll|clip|visible
    • .overflow_x|y_auto|hidden|scroll|clip|visible
    • .inline|inline_block|block|flex|grid
    • .display_none|contents
    • .flex_1
    • .float_none|left|right|inline_start|inline_end
    • .flip_x|y|xy
    • .font_sans|mono
    • .line_height_xs-xl
    • .size_xs-xl9
    • .icon_size_xs-xl3
    • .text_align_start|end|left|right|center|justify|justify_all|match_parent
    • .vertical_align_baseline|sub|super|text_top|text_bottom|middle|top|bottom
    • .font_weight_100-900
    • .ellipsis
    • .pixelated
    • .text_1-3
    • .darken|lighten_1-9
    • .bg|fg
    • .bg|fg_1-9
    • .color_darken|lighten_1-9
    • .color_bg|fg
    • .color_bg|fg_1-9
    • .hue_a-i
    • .color_a-i_1 -9
    • .bg_a-i_1-9
    • .border_color_1-5
    • .border_width_1-6
    • .outline_width_1-3
    • .border_none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset
    • .radius_0|50|100
    • .radius_xs3-xl
    • .fade_0|100
    • .fade_1-6
    • .shadow_xs-xl
    • .shadow_inset|outset_xs-xl
    • .shadow_a-i_xs-xl
    • .shadow_a-i_inset|outset_xs-xl
    • .shadow_inherit|initial|none
    • .w|h_0|100
    • .w|h_xs-xl15
    • .p|pt|pr|pb|pl|px|py_0
    • .p|pt|pr|pb|pl|px|py_xs5-xl15
    • .m|mx|my_auto
    • .m|mt|mr|mb|ml|mx|my_0
    • .m|mt|mr|mb|ml|mx|my_xs5-xl15
    • .gap_xs5-xl15
    • .column|row_gap_xs5-xl15

    Component classes
    #

    • .box
    • .row
    • .wrap|nowrap
    • .formatted
    • .width_xs-md
    • .selectable
    • .clickable
    • .pane
    • .panel
    • .icon_button
    • .plain
    • .menu_item
    • .chevron
    • .chip

    Raw CSS
    #

    @ryanatkn/moss/style_utilities.css
    @ryanatkn/moss/style_components.css

    colors #

    Color semantics
    #

    Moss provides a palette of color and hue variables designed to support concise authoring in light and dark modes, as well as straightforward theming by both developers and end-users at runtime. The colors have more semantics than just plain values, so they automatically adapt to dark mode and custom themes, at the cost of having different values depending on color scheme and theme.

    Adapting colors to dark mode

    A color's subjective appearance depends on the context in which it's viewed, especially the surrounding colors and values. Moss' semantic colors are designed to work across color schemes, so each Moss color variable has two values, one for light and one for dark mode. The exceptions are the lightest (1) and darkest (9) variants, although this may change if it yields better results.

    Custom themes

    Instead of "blue" and "red", colors are named with letters like "a" and "b", so you can change the primary "a" from blue to any color in a theme without breaking the name-to-color correspondence everywhere. This also flexibly handles more colors and cases than using names like "primary", and although it takes some learning, it's a simple pattern to remember. ("primary" and its ilk require learning too!)

    A downside of this approach is that changing a color like the primary "a" affects the many places it's used. Sometimes you may want to change the color of a specific element or state, not all the things. In those cases, use plain CSS and optionally Moss variables. Compared to most libraries, Moss provides fewer handles for granular color customizations, but the benefits include consistency, efficiency, DRY authoring, and ease of app-wide theming.

    Caveats
    #

    For performance reasons, Moss does not currently have an extensive set of variants, like specialized states for elements or color values like "blue". Each of the 7 hues has 9 HSL color values (e.g. hsl(120 55% 36%)) and 9 HSL values (e.g. 120 55% 36%, useful to apply custom alpha), handling most cases, and the base colors can be customized with platform APIs like the color-mix CSS function.

    Variants will be expanded when Moss includes a Vite plugin or other build tooling for optimization. A downside of removing unused styles is that they won't be available to your end-users at runtime. We'll probably end up with an interpreted language like Tailwind's just-in-time compiler.

    Hue variables
    #

    Hue variables contain a single hue number. Each color variable combines a hue variable with hardcoded saturation and lightness values for light and dark modes.

    Hue variables therefore provide a single source of truth that's easy to theme, but to achieve pleasing results, setting the hue alone is not always sufficient. Custom colors will often require you to set per-variable saturation and lightness values.

    Hue variables are also useful to construct custom colors not covered by the color variables. For example, Moss's base stylesheet uses hue_a for the semi-transparent ::selection. (try selecting some text - same hue!)

    Unlike the color variables, the hue variables are the same in both light and dark modes.

    • NaN
      primary
    • NaN
      success/help
    • NaN
      error/danger
    • NaN
      secondary
    • NaN
      tertiary
    • NaN
      quaternary
    • NaN
      quinary
    • NaN
      senary
    • NaN
      septenary

    Color variables
    #

    There are 9 variables per color, numbered 1 to 9, lightest to darkest. The 5th variable of each color is used as the base for things like buttons.

    Note that these values differ between light and dark modes! See the discussion above for why.

    These colors were eyeballed by a programmer, and will change :]

      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()
      • hsl()
        rgb()

    buttons #

    <button>a button</button>

    Buttons have a .selected state that can be used for various UI purposes, like showing a selected item in a menu or a styling button's aria-pressed state. Instead of having two distinct styles of buttons with outlined and filled variants, Moss makes filled buttons the default, and selected buttons are outlined. There's also the .deselectable modifier class for buttons that can be clicked when selected. Themes can customize this behavior.

    Colorful buttons
    #

    <button class="color_a">
    <button class="color_b">
    <button class="color_c">
    <button class="color_d">
    <button class="color_e">
    <button class="color_f">
    <button class="color_g">
    <button class="color_h">
    <button class="color_i">

    With disabled attribute
    #

    <button disabled>
    	:|
    </button>
    <button disabled>
    	a bigger disabled button
    </button>

    With .selected
    #

    <button class="selected">...</button>

    .selected buttons with .deselectable continue to be clickable when selected:

    <button class="selected deselectable">
    	...
    </button>

    With .plain and .icon_button
    #

    <button class="plain">
    	+
    </button>
    <button class="icon_button">
    	+
    </button>
    <button class="plain icon_button">
    	+
    </button>

    .selected variants

    <button class="plain selected">
    	+
    </button>
    <button class="icon_button selected">
    	+
    </button>
    <button class="plain icon_button selected">
    	+
    </button>

    .selected and .deselectable variants

    <button class="plain selected deselectable">
    	+
    </button>
    <button class="icon_button selected deselectable">
    	+
    </button>
    <button class="plain icon_button selected deselectable">
    	+
    </button>

    elements #

    Styles for plain HTML elements . See also typography and forms.

    #

    👇 hr
    #


    #

    a link in a p

    a link in a div

    a link with .selected

    #

    code in a p

    code in a div

    #

    a pre is
      preformatted
    					text
    code in a pre
    		 	is a
    				 block

    #

    Click this summary to see the rest of the details

    The children of the details excluding the summary.

    <details>
    	<summary>
    		Click this <code>summary</code>
    		to see the rest of the <code>details</code>
    	</summary>
    	<p>The children of the <code>details</code> excluding the <code>summary</code>.</p>
    	<Code code={'...'} />
    </details>

    #

    #

    <header>header</header>
    header

    #

    <footer>footer</footer>
    footer

    #

    <section>section</section>
    section

    ul
    #

    • a
    • b
    • see

    ul with .unstyled

    • a
    • b
    • see

    ol
    #

    1. one
    2. two
    3. etc

    ol with .unstyled

    1. one
    2. two
    3. etc

    menu
    #

  • 1
  • 2
  • 3
  • menu with .unstyled

  • 1
  • 2
  • 3
  • #

    <table>
    	<thead>
    		<tr>
    			<th>th</th>
    			<th>th</th>
    			<th>th</th>
    		</tr>
    	</thead>
    	<tbody>
    		<tr><td>td</td><td>td</td><td>td</td></tr>
    		<tr><td>td</td><td>td</td><td>td</td></tr>
    		<tr><td>td</td><td>td</td><td>td</td></tr>
    	</tbody>
    </table>
    ththth
    tdtdtd
    tdtdtd
    tdtdtd
    <table class="w_100">
    	...
    </table>
    ththth
    tdtdtd
    tdtdtd
    tdtdtd

    TODO more!

    forms #

    #

    <form>
    	<fieldset>
    		<legend>
    			a <Mdn_Link path="Web/HTML/Element/legend" />
    		</legend>
    		<label>
    			<div class="title">
    				username
    			</div>
    			<input
    				bind:value={username}
    				placeholder=">"
    			/>
    		</label>
    		...
    	</fieldset>
    	...
    </form>
    This is a legend

    More info can be included in <p> tags like this one. Here we could include info about passwords.

    form with range input
    #

    <input type="range" />
    <input type="range" disabled />

    form with checkboxes
    #

    form with radio inputs
    #

    typography #

    h1

    h2

    h3

    h4

    h5
    h6

    paragraphs

    paragraphs

    paragraphs

    p with some small text

    p sub p sup p

    show code

    Font sizes
    #

    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =

    Font weights
    #

    font-weight: 100
    font-weight: 200
    font-weight: 300
    font-weight: 400
    font-weight: 500
    font-weight: 600
    font-weight: 700
    font-weight: 800
    font-weight: 900

    Text colors
    #

    =
    =
    =

    Line heights
    #

    Icon sizes
    #

    = 18px
    🐢
    = 32px
    🐢
    = 48px
    🐢
    = 80px
    🐢
    = 128px
    🐢
    = 192px
    🐢
    = 256px
    🐢

    borders #

    Border colors
    #

    =
    =
    =
    =
    =

    Colorful border variants
    #

    =
    =
    =
    =
    =
    =
    =
    =
    =

    Border widths
    #

    =
    =
    =
    =
    =
    =

    Outline widths
    #

    =
    =
    =

    Border radius
    #

    =
    =
    =
    =
    =
    =
    =

    shading #

    Moss is designed around two simplistic models of light, one for dark mode and one for light mode. The goal is easy authoring with simple and consistent rules for arbitrary compositions and states. Each theme can choose to implement either light mode or dark mode or both.

    Light mode's starting point is plain white documents (like paper) where we can think of UI construction as assembling elements that contrast against the white background, replacing some of the white blankness with darkened values/color/shape. Black shadows on the white background make natural sense, and white glows against a white background are invisible.

    In contrast, dark mode's starting point is a lightless void where we add light. We add elements which emanate light. I think of videogames and virtual/augmented/actual reality. Black shadows are invisible against a black background, and white glows make natural sense against a black background.

    This distinction leads to complication. For example, applying a black shadow to an element has a particular visual impact on the typical light mode page, but viewed in dark mode, it would disappear completely against a black background.

    Moss provides APIs that simplify or hide this complexity. For example, the lighten and darken variables are the same in light and dark modes, but fg and bg are equivalent values that swap places in dark mode. Thus bg and fg are called color-scheme-aware, and lighten and darken are color-scheme-agnostic. (maybe you can think of better terminology? I like the word "adaptive" but idk) The colors docs elaborate more on this point.

    Opacity is used to enable arbitrary stacking that visually inherits its context. Not all cases are properly handled yet, and some choices are made for performance reasons, like avoiding opacity on text. (assuming this is still a thing?)

    Shades and highlights
    #

    darken and lighten
    #

    bg and fg
    #

    In light mode, bg is the same as lighten and fg is the same as darken. In dark mode, they're swapped.

    Fading opacity
    #

    full opacity

    Stacking opacity
    #

    <div style:background-color="var(--fg_1)" class="p_sm radius_xs3">
    	<div style:background-color="var(--fg_1)" class="p_sm radius_xs3">
    		<div style:background-color="var(--fg_1)" class="p_sm radius_xs3">
    			<div style:background-color="var(--fg_1)" class="p_sm radius_xs3">
    				<div style:background-color="var(--bg_4)" class="p_sm radius_xs3">
    					...
    				</div>
    			</div>
    		</div>
    	</div>
    </div>
    these variables have opacity, but notice how contrast changes with depth, creating limitations

    shadows #

    Glows
    #

    Colorful shadow variants
    #

    layout #

    Space variables
    #

    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =
    =

    Width variables
    #

    =
    =
    =