Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
ad5595b
add menu colors variables
dmitry-sinina Jun 7, 2026
f62025f
more variables
dmitry-sinina Jun 7, 2026
c617472
expose more variables. dark mode
dmitry-sinina Jun 8, 2026
b235fdd
scope buttons outline and configurable height
dmitry-sinina Jun 8, 2026
ff34d3d
fix menu overlapping
dmitry-sinina Jun 8, 2026
692c159
login form respect variables
dmitry-sinina Jun 8, 2026
e44e6e5
add active menu item text color variable
dmitry-sinina Jun 8, 2026
c37119e
refactor table tools buttons
dmitry-sinina Jun 9, 2026
2ac7763
disable pill higligting in #utility_nav and fix .col-selectable aligning
dmitry-sinina Jun 9, 2026
0cf0c46
pagination bar fixes
dmitry-sinina Jun 10, 2026
8357ed9
pagination bar fixes
dmitry-sinina Jun 10, 2026
54164df
skinTabPaddingX variable for tab headers padding
dmitry-sinina Jun 12, 2026
3528883
fix table
dmitry-sinina Jun 13, 2026
fdf7925
fix status tag centering
dmitry-sinina Jun 13, 2026
2043bf6
fix comments
dmitry-sinina Jun 17, 2026
8e5cf88
Merge master into dark_mode
Fivell Oct 2, 2026
88a7dde
Stop subtracting px from a radius the project chooses
Fivell Oct 2, 2026
0922733
Merge master into dark_mode
Fivell Oct 2, 2026
ad06861
Keep the menu font size off the li, where it leaks into the dropdown
Fivell Oct 2, 2026
37514df
Apply $skinMenuItemHoverTextColor, which was declared and never used
Fivell Oct 2, 2026
0584986
Make a checked table row look checked again
Fivell Oct 2, 2026
26cdefc
Give the batch-actions button its geometry, not the menu inside it
Fivell Oct 2, 2026
71462a1
Keep the hovered page number readable
Fivell Oct 2, 2026
18789c1
Document every variable the theme exposes
Fivell Oct 2, 2026
3b96ead
Derive the focus accent from the theme accent
Fivell Oct 2, 2026
01dc035
Paint the dialog's primary button from the button palette it hovers to
Fivell Oct 2, 2026
095c3a7
Give buttons a label colour to go with their fill
Fivell Oct 2, 2026
06bcdd9
Make status tag labels readable on their own fills
Fivell Oct 2, 2026
24adbcb
Give the index view switcher the segmented geometry it shares with sc…
Fivell Oct 2, 2026
c1df2d0
Drop fourteen variables nobody needs before they become API
Fivell Oct 2, 2026
0eaeaa1
Keep $skinBorderWindowColor working for index table headers
Fivell Oct 2, 2026
0af584e
Let a.delete_link keep the delete colour without data-method
Fivell Oct 2, 2026
b93be25
Darken the default link colour until it passes on every light surface
Fivell Oct 2, 2026
0b84a62
Put back the four tab and table-header variables — they are in use
Fivell Oct 2, 2026
a69edb1
Accept $skinHeaderPaddingY again, as a shorthand for both halves
Fivell Oct 2, 2026
973a76c
Repaint the dropdown list and its dividers for dark mode
Fivell Oct 2, 2026
34f824b
Keep the title-bar action button's hover off its own dropdown entries
Fivell Oct 2, 2026
e14ab8e
Fix the record count line everywhere, not only under .comments
Fivell Oct 2, 2026
13bbdc7
Style the plain dropdown button in the tool row like its neighbours
Fivell Oct 2, 2026
814c219
Address the datepicker by id, the way everything else addresses it
Fivell Oct 2, 2026
511b004
Write down what changes shape for projects already setting variables
Fivell Oct 2, 2026
ec31e59
Show a real configuration, not a three-line stub
Fivell Oct 2, 2026
238772e
Outline the dropdown nipple instead of filling it flat
Fivell Oct 2, 2026
3698881
Leave the datepicker header alone — it was already right in both modes
Fivell Oct 2, 2026
f827bfd
Lift tool buttons and dropdown panels off the dark page
Fivell Oct 2, 2026
f8624b2
Show the theme as it actually looks now, in both modes
Fivell Oct 2, 2026
8a5a593
Adopt the configuration this theme is actually used with as the defaults
Fivell Oct 2, 2026
4f5c10d
Show the whole admin in one image per mode
Fivell Oct 2, 2026
5ee2f7d
Tick two rows in the screenshot, not all of them
Fivell Oct 2, 2026
0db2616
Give the dark table header its own step again
Fivell Oct 2, 2026
90eb4c3
Rebuild the dark palette as a ladder of distinguishable steps
Fivell Oct 2, 2026
5237d47
Keep dark-mode buttons in the theme's own accent
Fivell Oct 2, 2026
c1c0d5a
Document how to switch themes, since nothing here does it
Fivell Oct 2, 2026
c8f86d0
Reshoot both modes on the finished palette
Fivell Oct 2, 2026
310a0a8
Keep the delete link readable on the rows it is read from
Fivell Oct 2, 2026
bd43e5a
Ship a theme switch, since ActiveAdmin 3 has none
Fivell Oct 2, 2026
e93d23f
Show the switch rather than only describing it
Fivell Oct 2, 2026
787722b
Make the input focus indicator actually win
Fivell Oct 2, 2026
f8ca0cb
Document the form and filter controls at full size
Fivell Oct 2, 2026
54c6a59
Rebuild the overview shots so nothing is cut mid-row
Fivell Oct 2, 2026
9a294b5
Fill the gutters with the page colour
Fivell Oct 2, 2026
7e9336e
Let the dropdown hover colour actually show
Fivell Oct 2, 2026
cd9d466
Ship an optional theme switch
Fivell Oct 2, 2026
159aef2
Make $skinHeaderPaddingY do what it documents
Fivell Oct 2, 2026
e44c7a8
Stop the link rule out-weighing the components it excludes
Fivell Oct 2, 2026
6c220c1
Darken the button on hover instead of lightening it
Fivell Oct 2, 2026
9f8b882
Keep the switch working where localStorage throws
Fivell Oct 2, 2026
63f4c4e
Ship the theme switch in the npm package
Fivell Oct 2, 2026
a81379d
Type-guard the whole palette, not a quarter of it
Fivell Oct 2, 2026
757bb82
Derive the active tab label from the accent instead of repeating it
Fivell Oct 2, 2026
f258a22
Rewrite the variables table from the declarations, and keep it there
Fivell Oct 2, 2026
138fd21
Reshoot on the fixed palette
Fivell Oct 2, 2026
f43472b
Darken the primary button so its white label reads
Fivell Oct 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
249 changes: 241 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,255 @@ In your base stylesheet entry point `active_admin.scss` (as example), add line:
@import '@activeadmin-plugins/active_admin_theme';
```

You can change basic colors of the theme by setting some variable above active_admin_theme import line in active_admin.css.scss
## Customising

```css
...
$skinMainFirstColor: #A5A7AA!default;
$skinMainSecondColor: #0066CC!default;
$skinBorderWindowColor: #B8BABE!default;
Set any of the variables below *above* the import line:

```scss
$skinMainFirstColor: #A5A7AA;
$skinMainSecondColor: #0066CC;
$skinBorderWindowColor: #B8BABE;

@import 'wigu/active_admin_theme';
```

Variables are typed. A value of the wrong kind — `none` where a colour is
expected, or a length without its unit — fails the build with a message
naming the variable, instead of silently emitting CSS the browser discards.

### Dark mode

The theme follows the operating system via `prefers-color-scheme`, and can be
pinned per page with `data-theme="light"` or `data-theme="dark"` on `<html>`.
Every colour below that has a `…Dark` twin is what dark mode uses; each twin
defaults to its light counterpart unless noted, so a project that only sets
the light value keeps one consistent colour in both modes.

### A worked example

The defaults are the configuration this theme is run with in
[yeti-web](https://github.com/yeti-switch/yeti-web). To go back to the blue
header the theme shipped before:

```scss
$skinMenuPillColor: $skinMainSecondColor;
$skinMenuPanelColor: $skinMainSecondColor;
$skinMenuTextColor: #ffffff;
$skinMenuItemHoverColor: transparent;
$skinMenuItemHoverTextColor: #ffffff;
$skinMenuFontSize: 1em;
$skinHeaderPaddingY: 7px; // one value top and bottom
$skinTitleBarColor: lighten($skinMainFirstColor, 8%);
$skinTitleBarBorderWidth: 3px;
$skinPanelHeaderColor: $skinMainSecondColor;
$skinPanelHeaderTextColor: #ffffff;
$skinTabInactiveColor: $skinMainSecondColor;
$skinInactiveTabTextColor: #ffffff;
$skinLinkColor: $skinMainSecondColor;

@import 'wigu/active_admin_theme';
...
```

Note `var(--aa-page-bg)` works as a variable value: a custom property follows
the mode on its own, so one line covers both themes.

### Switching themes

The stylesheet follows the operating system on its own and honours
`data-theme="light"` or `data-theme="dark"` on `<html>`. A switch is optional
and ships with the gem:

```scss
// app/assets/javascripts/active_admin.js
//= require wigu/theme_toggle
```

```js
// or, as an npm module
import "@activeadmin-plugins/active_admin_theme/src/theme_toggle";
```

A gem cannot add a menu item: ActiveAdmin builds the utility navigation from
the host application's initializer, and nothing in a stylesheet or an asset
runs at that point. So with nothing else to do, the script injects its own
`li#theme_toggle` into `#utility_nav` on load. That works, but the item is
inserted before the server-rendered ones and is not yours to order or hide.

Declaring it yourself costs four lines and puts it under your control — this is
how [yeti-web](https://github.com/yeti-switch/yeti-web) does it:

```ruby
# config/initializers/active_admin.rb
config.namespace :admin do |admin|
admin.build_menu :utility_navigation do |menu|
# A real url, not "#": ActiveAdmin drops a blank utility item.
menu.add id: "theme_toggle", label: "", url: "#theme",
priority: 9_999_998, html_options: { role: "button" }
end
end
```

The script finds `#theme_toggle` or anything carrying `.dark-mode-toggle`,
binds by delegation — so the control survives a re-render — and writes nothing
to the page but `data-mode`. Everything visible comes from the stylesheet.

[![Theme switch](./img/switch.png)](./img/switch.png)

Below the two pages: the three states at rest — half circle for **auto**, sun
for **light**, moon for **dark** — then the last two hovered. A click moves to
the next state, so the control costs the width of one icon in a header that is
usually already full. The title says where that click goes, since one icon
cannot show both.

`auto` removes the attribute, so the media query decides and the page follows
the operating system live; the other two pin the choice in `localStorage` under
`aa-theme`. ActiveAdmin 4's own toggle writes `light` or `dark` on the first
click and never writes `auto` back, so a user there cannot return to following
the system without clearing storage by hand — hence the third state.

The glyphs are inline SVG used as a CSS `mask`, so the gem still ships no image
files, there is nothing for a host application's CSP to allow, and the icon
takes `currentColor` — `$skinMenuTextColor` like the rest of the bar, and the
hover colour on hover. To use your own icon font instead, override
`$theme-icon-auto` / `$theme-icon-light` / `$theme-icon-dark`, or restyle
`#theme_toggle > a:before` outright.

The control is a link with no destination, so <kbd>Tab</kbd> reaches it and
<kbd>Enter</kbd> and <kbd>Space</kbd> operate it. Changing the theme in one tab
applies it in the others.

### Upgrading

Two things changed shape in this release and are worth knowing if you already
set variables:

* Dropdown panels — the title-bar menu, the batch-actions menu and the
table-tools menus — now follow the surface palette (`$skinSurfaceColor` and
friends) instead of `$skinMainFirstColor` / `$skinMainSecondColor`. That is
what lets them work in both modes. If you branded those panels through the
two main colours, point `$skinSurfaceColor` and `$skinSurfaceHoverColor` at
the same values.
* The primary button fill is darker (`darken($skinMainSecondColor, 20%)` rather
than the accent itself). White on the accent is 2.74:1, under the 4.5:1 small
text needs, and dark mode was already using this tone — the button is now one
colour in both modes. Set `$skinButtonColor: $skinMainSecondColor;` for the
old look.
* The default content link colour is darker (`#1f5f8d` rather than the accent).
The accent is a fill colour and failed WCAG AA as body text. Set
`$skinLinkColor` back to `$skinMainSecondColor` if you prefer the old look.

### Variables

#### Core

| Variable | Default (light / dark) | |
|---|---|---|
| `$skinMainFirstColor` | `#23282f` | |
| `$skinMainSecondColor` | `#5ea3d3` | |
| `$skinBorderRadius` | `4px` | |
| `$skinBorderWindowColor` | `#e6e9ee` | |
| `$skinTablePadding` | `10px` | |

#### Surfaces, text and borders

| Variable | Default (light / dark) | |
|---|---|---|
| `$skinPageBgColor` / `$skinPageBgColorDark` | `#f7f9fb` / `#161a1e` | page background |
| `$skinSurfaceColor` / `$skinSurfaceColorDark` | `#ffffff` / `#25292f` | panels / cards / content |
| `$skinSurface2Color` / `$skinSurface2ColorDark` | `#f0f2f5` / `#2c3137` | table headers / striping / subtle fills |
| `$skinSurfaceHoverColor` / `$skinSurfaceHoverColorDark` | `#f5f7fa` / `#3f454d` | row / item hover |
| `$skinSelectedRowColor` / `$skinSelectedRowColorDark` | `#d9e4ec` / `#304457` | checked table row |
| `$skinElevatedColor` / `$skinElevatedColorDark` | `$skinSurfaceColor` / `#363c43` | tool buttons and dropdown panels floating above the page |
| `$skinTextColor` / `$skinTextColorDark` | `#323537` / `#dde2e8` | body text |
| `$skinTextMutedColor` / `$skinTextMutedColorDark` | `#6b7177` / `#b0b8c2` | secondary text / axis labels |
| `$skinBorderColor` / `$skinBorderColorDark` | `#e0e4e9` / `#404750` | borders / grid lines |
| `$skinInputBgColor` / `$skinInputBgColorDark` | `#ffffff` / `#1e2227` | form control background |
| `$skinInputBorderColor` / `$skinInputBorderColorDark` | `#c9ced4` / `#4d555f` | form control border |

#### Header menu

| Variable | Default (light / dark) | |
|---|---|---|
| `$skinMenuPillColor` | `#2e3236` | top-level current/hover pill |
| `$skinMenuPillTextColor` | `#6cb0de` | text on that pill; follows the dropdown text so a |
| `$skinMenuPanelColor` | `#2e3236` | dropdown panel bg + hover "bridge" border |
| `$skinMenuTextColor` | `#dfe2e6` | dropdown item text (was: inherited #fff) |
| `$skinMenuItemHoverColor` | `#3f454c` | dropdown item hover/current bg (was: none) |
| `$skinMenuItemHoverTextColor` | `#6cb0de` | hover/current dropdown item text, same reason |
| `$skinMenuFontSize` | `13px` | header menu text size |
| `$skinMenuItemPaddingY` | `5px` | dropdown item top/bottom padding (was 6px/4px + a 7px border) |
| `$skinMenuItemLineHeight` | `1.35` | dropdown item line-height |
| `$skinMenuPanelMaxWidth` | `260px` | dropdown panel ceiling; longer labels wrap instead of leaving the viewport |
| `$skinHeaderPaddingY` | `null` | sets both halves at once |
| `$skinHeaderPaddingTop` | `4.5px` | header top padding (base value, kept so the header does not shift) |
| `$skinHeaderPaddingBottom` | `4.5px` | header bottom padding |
| `$skinHeaderLogoMaxHeight` | `none` | cap the site_title logo image height |

#### Title bar

| Variable | Default (light / dark) | |
|---|---|---|
| `$skinTitleBarColor` | `#343c46` | |
| `$skinTitleBarBorderColor` | `$skinMainSecondColor` | |
| `$skinTitleBarBorderWidth` | `0` | |
| `$skinTitleBarButtonPaddingY` | `6px` | action button vertical padding |
| `$skinTitleBarButtonPaddingX` | `10px` | action button horizontal padding |

#### Panels, tabs and labels

| Variable | Default (light / dark) | |
|---|---|---|
| `$skinPanelHeaderColor` / `$skinPanelHeaderColorDark` | `var(--aa-page-bg)` / `var(--aa-page-bg)` | |
| `$skinPanelHeaderTextColor` / `$skinPanelHeaderTextColorDark` | `var(--aa-inactive-tab-text)` / `var(--aa-inactive-tab-text)` | |
| `$skinPanelHeaderPaddingY` | `5px` | panel + sidebar header height |
| `$skinLabelColor` / `$skinLabelColorDark` | `#8494a8` / `$skinTextColorDark` | |
| `$skinTabInactiveColor` / `$skinTabInactiveColorDark` | `#f7f9fb` / `#161a1e` | inactive tab fill |
| `$skinActiveTabTextColor` / `$skinActiveTabTextColorDark` | `$skinMainSecondColor` / `#7cc0ec` | selected tab label |
| `$skinInactiveTabTextColor` / `$skinInactiveTabTextColorDark` | `#5e6469` / `#b0b8c2` | inactive tab label |
| `$skinTableHeaderTextColor` / `$skinTableHeaderTextColorDark` | `#5e6469` / `#dde2e8` | index-table column header text |
| `$skinTabPaddingY` | `8px` | tab height |
| `$skinTabPaddingX` | `15px` | tab label horizontal padding (text → border) |

#### Buttons and table tools

| Variable | Default (light / dark) | |
|---|---|---|
| `$skinButtonColor` / `$skinButtonColorDark` | `darken($skinMainSecondColor, 20%)` / `$skinButtonColor` | one tone in both modes; white on it is 5.35:1 |
| `$skinButtonTextColor` / `$skinButtonTextColorDark` | `#ffffff` / `$skinButtonTextColor` | label on those buttons |
| `$skinTableToolsHeight` | `30px` | |
| `$skinTableToolsPaddingX` | `$skinTableToolsHeight * 0.4` | 12px at 30px |

#### Links

| Variable | Default (light / dark) | |
|---|---|---|
| `$skinAccentColor` / `$skinAccentColorDark` | `$skinMainSecondColor` / `$skinAccentColor` | focus ring / accent outline |
| `$skinLinkColor` / `$skinLinkColorDark` | `#38678b` / `#7cc0ec` | |
| `$skinDeleteLinkColor` / `$skinDeleteLinkColorDark` | `$skinLinkColor` / `#f49b9b` | |

## Screen

<a href="./img/wigu.png"><img src="./img/wigu.png"></a>
Index with filters, show page, nested `has_many` form, an open batch-actions
menu and the datepicker — the same admin in both modes. The theme follows the
operating system and can be pinned per page with `data-theme`.

#### Light

[![Light](./img/light.png)](./img/light.png)

#### Dark

[![Dark](./img/dark.png)](./img/dark.png)

#### Form and filter controls

Shown at full size, because the two shots above scale the controls down past
the point where you can tell what colour they are. Inputs, selects and
textareas take `$skinInputBgColor` / `$skinInputBorderColor` — white on
`#c9ced4` in light mode, a recessed `#1e2227` well on `#4d555f` in dark — and
the focused field (`Name`, `Title`) carries `$skinMainSecondColor`.

[![Form and filter inputs](./img/inputs.png)](./img/inputs.png)

## Contributing

Expand Down
Loading
Loading