π¨π its-swissv1.1.0
2026.08.31 updated 2026.09.24
sensible Swiss defaults for Rails
readme
sensible Swiss defaults for Rails
Tokens, reset, typography, grid primitives and components in the spirit of the International Typographic Style, as plain CSS plus a Rails engine that ships it. Monochrome by default: the value scale does the work, and the one accent is the consuming application's to set.
quickstart
# Gemfile
gem "its-swiss", github: "bobbymeyer/its-swiss"bundle install
bin/rails generate its_swiss:installThe generator fills the four slots an application owns:
| It writes | Which is |
|---|---|
app/assets/stylesheets/theme.css |
The accent, the typeface, the value scale, the field count, the baseline |
app/views/layouts/application.html.erb |
A nested layout inside the shell β no layout line on any controller |
| A route for the specimen | Development only; skipped if already mounted |
Everything else is a gem dependency: Propshaft finds the stylesheets, and the engine pins its own JavaScript, so upgrading the gem is enough.
Specimen β β every component, the type scale, the value scale and the grid primitives, rendered by the library and published from this repository.
the shell
layouts/its_swiss/shell carries the view-transition opt-in, the stylesheets
in layer order, a skip link, and the slots. All optional:
| Slot | Is |
|---|---|
:title |
The page's title; falls back to the application's name |
:head |
Anything else belonging in <head> |
:mark |
The wordmark. No mark and no nav means no masthead at all |
:nav |
The destinations |
:subnav |
A second layer, inside the one you are in |
:main_class |
What the page's main region is, if it is a grid |
:footer |
Whatever belongs after the page |
It stops there. A page layout beyond the shell is the application's, for the same reason its grid is.
tokens
Set in theme.css; everything else reads them.
| Group | Custom properties |
|---|---|
| Value scale | --value-chroma, --value-hue, --value-0 β¦ --value-5 |
| Roles | --paper, --paper-shaded, --rule, --rule-strong, --ink-quiet, --ink, --accent, --accent-ink |
| Rules | --rule-hair, --rule-heavy |
| Type | --font-family, --font-mono, --face-100, --face-150, --face-200, --face-mono, --cap-correction |
| Scale | --size-1 β¦ --size-5 |
| Baseline | --line, --line-2, --line-3, --line-4, --half-line, --module |
| Space | --space-unit, --space-1, --space-2, --space-3, --space-4, --space-6, --space-8 |
| Page | --columns, --gutter, --page-max, --page-inset, --field |
| Measure | --measure, --measure-characters, --card, --filter-label |
| Transitions | --transition-page, --transition-morph, --transition-ease |
grid primitives
.page, .grid, .run, .stack, .modules, .fields, .measure.
components
| Class | Is |
|---|---|
.masthead, .nav, .subnav, .menu, .menu--current, .skip-link |
Navigation |
.page-head, .sections, .footer |
Page furniture |
.button, .button--primary, .button--quiet, .button--danger |
Buttons |
.form, .form--inline, .form--dense, .field, .field--inline, .field--invalid, .choice, .errors |
Forms |
.filters, .filter |
Filter bars |
.cards, .card |
Cards |
.table, .numeric |
Tables |
.pairs, .pairs--stacked |
Definition pairs |
.figure, .figure--cover, .figure--modular |
Figures |
.pagination |
Pagination |
.md, .measure, .copy, .explain, .link-quiet, .empty |
Prose and states |
.css, .components, .visually-hidden, .no-metric-overrides, .rule--heavy |
Utilities |
helpers
| Helper | Does |
|---|---|
its_swiss_form_with |
The form builder |
its_swiss_stylesheet_tags |
The seven stylesheets, in layer order |
its_swiss_page_numbers |
Page numbers for pagination |
its_swiss_application_name |
The application's name |
its_swiss_face, its_swiss_typeface |
The typeface slots |
page_head, page_sections |
Page head and section destinations |
nav_link_to, nav_menu |
Navigation |
search_form, filter_register |
Filter bars |
copy_button |
A value that copies itself |
explain |
An explanatory note |
Partials: its_swiss/shared/masthead, flash, errors, pagination.
form builder
its_swiss_form_with adds field, select, collection_select,
check_box, radio_button, text_area and submit, each rendering its own
label, hint and error, wired with aria-describedby.
JavaScript
Three Stimulus controllers, registered by the module the shell imports. The host adds nothing to its importmap.
| Controller | Does |
|---|---|
its-swiss-clipboard |
A value that copies itself |
its-swiss-live-search |
Search on an index |
its-swiss-mono |
Monospace metric handling |
the cascade
Every file states its own layer, and the order is declared before the first import:
@layer its-swiss.tokens, its-swiss.faces, its-swiss.reset, its-swiss.type,
its-swiss.grid, its-swiss.components, its-swiss.transitions;Everything is inside @layer, so any application rule wins without effort. A
stylesheet the application adds after the library takes its place in the order
from where its layer is first named.
its-swiss.css is the whole library in one <link>; the Rails helper writes
the seven separately. Both resolve identically.
tech
Rails engine, v1.1.0. Ruby >= 3.2. 1,457 lines of CSS in seven files. No build step and no CSS framework.
bundle exec rake test150 runs. 30 of them drive a real browser through Capybara and Selenium, and need a Chrome matching the installed ChromeDriver. The specimen is measured in Chromium, WebKit and Firefox before it is published.
license
MIT.