Theming
Theming is how you make Avo look like your product — its colors, fonts, surfaces, logos, and chrome.
The fastest way to theme Avo is to let an AI agent do it for you. Everything Avo exposes for theming is plain, well-structured text — Ruby config, CSS variables, ERB — which is exactly what an LLM handles well. Point your coding agent at this guide, describe the look you want, and it can produce a complete, coherent theme far faster than hand-tuning values one at a time. Skip to the ready-made prompts →
Hand your agent these pages
Theming and customizing draws on a handful of Avo surfaces. Give your agent any of these (or read them yourself) — together they're the full toolbox:
- Custom CSS & styling — the TailwindCSS integration and the
avo-overrides.css/avo-overrides.jsno-build escape hatch - JavaScript & Stimulus — add behavior to any screen
- Asset handling — load your own CSS/JS through the app pipeline
- Ejecting views — copy Avo's markup into your app and rewrite it
- Custom tools & Resource tools — build entirely new UI you style yourself
Avo::ApplicationController— hook into Avo's controller layer- Plugins — package styles, scripts, and behavior as a reusable extension
- Internals — the lower-level building blocks Avo exposes
Do it by hand
Everything the agent does, you can do yourself. Avo gives you a ladder of approaches, from a few lines of Ruby to full CSS control — pick the one that matches how far you want to go.
| You want to… | Reach for | Build step |
|---|---|---|
| Swap logos, pick a color scheme and accent/neutral | Appearance config | none |
| Re-skin the whole UI by overriding colors and radii | CSS variables | none |
| Use your own typeface | Font variables | none |
| Restyle specific components (navbar, sidebar, tables) | Component CSS variables | none |
| Style your own custom tools, fields, or pages | Tailwind & custom CSS | Tailwind |
| Rewrite a view's markup outright | Eject a view for full control | none |
Most apps only ever touch the first two rows. Start there and go deeper only when you hit something the higher layer can't express — the further down you go, the more you own.
Recolor with the appearance config
The fastest way to brand Avo is config.appearance — set your logos, default color scheme, and pick a neutral and accent palette (built-in presets or your own brand colors), all from Ruby with no build step.
# config/initializers/avo.rb
Avo.configure do |config|
config.appearance = {
logo: "my_company/logo.png",
neutral: :slate,
accent: :blue
}
endThis covers the palette that drives most of the interface. For everything it deliberately leaves out — navbar background, sidebar surfaces, table row affordances, focus rings, motion — drop to CSS variables below. See the Appearance guide for the full palette, logo, persistence, and locking options.
Re-skin with CSS variables
Avo's entire look is driven by CSS custom properties, so overriding a handful of variables re-skins the whole interface — no @apply, no build step. The quickest surface for this is avo-overrides.css: an empty file Avo ships and loads on every screen, after its own stylesheet, so your rules win the cascade.
Eject it into your app, then override whatever you like:
rails g avo:eject --partial :avo_overrides_css/* app/assets/stylesheets/avo-overrides.css */
:root {
--color-accent: var(--color-fuchsia-500);
--color-accent-content: var(--color-fuchsia-600);
--radius-card: 1.5rem;
}
.dark {
--color-accent: var(--color-fuchsia-400);
}Set light-mode values on :root and dark-mode-specific values on .dark when you need them to differ.
Let an AI agent do it
Because it's plain, well-named CSS variables in a single file, this is the surface where an LLM shines — hand it this guide and a vibe, and it can write a complete, coherent theme in one shot. Copy a prompt below (pick a theme in the tabs) and paste it into your coding agent:
Use the Avo theming guide at https://docs.avohq.io/4.0/theming.html and reskin my Avo app to a coastal theme — soft sand neutrals, sea-glass and deep-ocean accents. Override CSS variables in app/assets/stylesheets/avo-overrides.css only, with matching :root and .dark values.Use the Avo theming guide at https://docs.avohq.io/4.0/theming.html and reskin my Avo app to a rose theme — warm blush neutrals and a rich rose accent. Override CSS variables in app/assets/stylesheets/avo-overrides.css only, with matching :root and .dark values.Use the Avo theming guide at https://docs.avohq.io/4.0/theming.html and reskin my Avo app to an 80's sunset theme — dusk purples and hot magenta-to- orange accents. Override CSS variables in app/assets/stylesheets/avo-overrides.css only, with matching :root and .dark values.TIP
avo-overrides.css is served as-is — it is not run through the Tailwind build. For custom utility classes (@apply, arbitrary values) in your own UI, use the TailwindCSS integration instead.
Change the font
Avo ships Inter and reads it through a single variable, --font-sans, which every screen inherits. Monospace text — code snippets in alerts, file details in the media library — reads --font-mono. Point either at another family in avo-overrides.css and the whole interface follows, with no build step:
/* app/assets/stylesheets/avo-overrides.css */
:root {
--font-sans: "IBM Plex Sans", system-ui, sans-serif;
--font-mono: "IBM Plex Mono", ui-monospace, monospace;
}That is the entire change for a font the visitor's device already has, such as a system font stack. For any other typeface, load it first — pick one of the two approaches below.
Avo sets text at weights 400, 500, 600, and 700. Load all four (or a variable font covering that range) — otherwise the browser synthesizes the missing weights.
Load a hosted font
Every font service hands you a stylesheet URL. Paste it as a @import at the very top of avo-overrides.css — CSS requires imports before any other rule — and set the variable below it:
/* app/assets/stylesheets/avo-overrides.css */
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&display=swap");
:root {
--font-sans: "IBM Plex Sans", system-ui, sans-serif;
}If you'd rather load it with <link> tags — the service's copy-paste snippet, with preconnect hints — eject the :head partial and drop them in. Fonts don't compete in the cascade, so it doesn't matter that :head renders after Avo's assets (:pre_head, which renders before them, works just as well). The variable override still lives in avo-overrides.css:
<%# app/views/avo/partials/_head.html.erb %>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&display=swap">Where to get that URL:
| Service | Stylesheet URL | Notes |
|---|---|---|
| Google Fonts | https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&display=swap | The largest library. Pick the four weights in the "Get font" panel. |
| Bunny Fonts | https://fonts.bunny.net/css?family=ibm-plex-sans:400,500,600,700 | Same library as Google Fonts, served without tracking — the GDPR-friendly swap. |
| Fontshare | https://api.fontshare.com/v2/css?f[]=satoshi@400,500,700&display=swap | Indian Type Foundry's free catalog — a different flavor from the Google set. |
| Adobe Fonts | https://use.typekit.net/<kit-id>.css | Commercial faces bundled with Creative Cloud. Add the site's domain to the kit. |
WARNING
If your app sets a Content Security Policy, allow the font host in font-src — and in style-src when the font's stylesheet is fetched from there too.
Self-host the font files
Drop the files under public/fonts/ and declare them with @font-face in avo-overrides.css. Referencing them by absolute path keeps the same file working under Propshaft and Sprockets alike, with no digest lookup involved. For a Google Fonts family, google-webfonts-helper packages the .woff2 files and the matching @font-face block:
/* app/assets/stylesheets/avo-overrides.css */
@font-face {
font-family: "IBM Plex Sans";
font-style: normal;
font-weight: 400 700;
font-display: swap;
src: url("/fonts/ibm-plex-sans-variable.woff2") format("woff2");
}
:root {
--font-sans: "IBM Plex Sans", system-ui, sans-serif;
}That declaration covers a variable font. For static files, write one @font-face per weight, each with a single font-weight value.
Fine-tune specific components
Individual components expose their own variables — the top navbar palette, sidebar background, table row hover/selected states, focus ring, and more. Override them in avo-overrides.css (above) or, if you'd rather keep theming next to the rest of your <head>, eject the :head partial:
bin/rails generate avo:eject --partial :head<%# app/views/avo/partials/_head.html.erb - append in the file %>
<style>
.top-navbar {
--top-navbar-background: #1e3a5f;
--top-navbar-content: #dbeafe;
}
.avo-sidebar {
--sidebar-background: #f5f7fa;
--sidebar-border: #d6dbe2;
}
</style>Avo renders the :head partial after its bundled stylesheets, so these take precedence too. The full list of component variables, with defaults and descriptions, lives in the CSS variables reference.
Set a background image
The main content panel takes a solid color from --color-main-content-background. For an image or gradient instead, there's no config option — set background-image on .main-content in avo-overrides.css. It layers over that color, so keep an opaque image or leave the color as the fallback for any area the image doesn't cover:
/* app/assets/stylesheets/avo-overrides.css */
.main-content {
background-image: url("/my-background.png"); /* a file in public/, or a full URL */
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
/* Or a gradient — no asset needed: */
/* .main-content { background-image: linear-gradient(135deg, #dbeafe, #fce7f3); } */Scope a different image per scheme with .dark .main-content { background-image: … }.
Style your custom UI
Theming variables cover Avo's own screens. When you build custom tools, custom fields, or resource tools, you're writing your own markup — style it with Tailwind utility classes or your own CSS:
- Tailwind classes in custom UI — the TailwindCSS integration compiles utility classes used in your extension points, so classes that aren't in Avo's precompiled bundle still work.
- Your own stylesheets and scripts — Asset handling covers loading custom CSS/JS through your app's pipeline; the JavaScript guide covers adding behavior with Stimulus.
Eject a view for full control
When even CSS can't express what you need — you want to change the markup itself, not just its styling — eject the view or partial into your app and rewrite it. This is the deepest level of control Avo offers.
bin/rails generate avo:eject --component Avo::Views::ResourceIndexComponentYou own ejected views from here on
An ejected file is a frozen copy of Avo's markup at the moment you generated it. Avo no longer controls it, so improvements and fixes shipped in later versions won't reach your copy — you're accountable for reviewing Avo's changes on each upgrade and porting anything you want into your ejected file. Prefer the CSS-variable and Tailwind layers above whenever they can do the job; reach for ejecting only when they genuinely can't.