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.
# 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.
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.
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 |
.page, .grid, .run, .stack, .modules, .fields, .measure.
| 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 |
| 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.
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.
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 |
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.
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.
MIT.