Install LocoMotion for Rails

LocoMotion is both a set of guides for building Ruby on Rails applications as well as a Component library that you can install into an existing Rails application.

This guide walks you through installing the Component library.

1. Gem Installation

Add the following to your Gemfile and run bundle install — or grab main straight from GitHub if you want the latest and greatest.

# Gemfile
gem "loco_motion-rails", "~> 0.6.0", require: "loco_motion"
# Gemfile
gem "loco_motion", github: "profoundry-us/loco_motion", branch: "main", require: "loco_motion"

2. Javascript Installation

Next, some of the components require Javascript. Install the library with your package manager of choice.

npm i -D @profoundry-us/loco_motion@latest
yarn add -D @profoundry-us/loco_motion@latest

Now, import and register the relevant controllers in your controllers/index.js file.

// app/javascript/controllers/index.js

import { Application } from "@hotwired/stimulus"
const application = Application.start()

// Import LocoMotion controllers
import { CountdownController, ThemeController, CallyInputController, ModalController } from "@profoundry-us/loco_motion"

application.register("loco-countdown", CountdownController)
application.register("loco-theme", ThemeController)
application.register("loco-cally-input", CallyInputController)
application.register("loco-modal", ModalController)

The loco-modal controller is only required for the Modal's programmatic open/close API and Global Modal features — basic modals work without it.

Cally Javascript Setup

Note that there is an extra step if you want to use the Cally Calendar or CallyInput components.


See the CallyInput Docs for more information.

3. TailwindCSS Installation

LocoMotion uses Tailwind 4, which configures plugins directly in your CSS file rather than in tailwind.config.js.

3a. CSS Setup

Add the following to your application.tailwind.css (or equivalent):

/* application.tailwind.css */

/* Import the base Tailwind theme */
@import 'tailwindcss';

/* Include DaisyUI via @plugin directive */
@plugin 'daisyui' {
  themes: light --default, dark --prefersdark;
}

/* LocoMotion's custom variants (`where`, `dark`) and component-required
   utilities, shipped as a single versioned file via the npm package. */
@import '@profoundry-us/loco_motion/loco.css';

/* Point to tailwind.config.js for content scan paths */
@config "../tailwind.config.js";
Why the @import?

@import '@profoundry-us/loco_motion/loco.css' pulls in the custom variants and component-required utility rules that LocoMotion's components depend on (the where and dark variants, the floating-sticky label helper, and the keyboard-focus tooltip reveal). It resolves through the @profoundry-us/loco_motion npm package you installed above, so you stay in sync across upgrades instead of hand-copying a growing block of CSS. Keep @import 'tailwindcss', the daisyui plugin, and @config in your own entry — those are app-level choices.

3b. Content Path Setup

Next, create or update your tailwind.config.js to tell Tailwind where to scan for class names. In Tailwind 4 this file handles only content paths — plugins belong in your CSS file (see above). Scan every LocoMotion component, or list just the ones you use for an even smaller bundle (at the cost of updating the list every time you adopt a new component):

// tailwind.config.js
const { execSync } = require('child_process');

// Get the path to the loco_motion gem
let locoBundlePath = execSync('bundle show loco_motion-rails').toString().trim();

module.exports = {
  content: [
    `${locoBundlePath}/app/components/**/*.{rb,js,html,haml}`,
    'app/components/**/*.{rb,js,html,haml}',
    'app/views/**/*.{rb,js,html,haml}',
  ]
}
// tailwind.config.js
const { execSync } = require('child_process');

// Get the path to the loco_motion gem
let locoBundlePath = execSync('bundle show loco_motion-rails').toString().trim();

module.exports = {
  content: [
    `${locoBundlePath}/app/components/daisy/actions/button_component.{rb,haml}`,
    `${locoBundlePath}/app/components/daisy/data_display/countdown_component.{rb,haml}`,
    `${locoBundlePath}/app/components/daisy/data_display/countdown_controller.js`,
    /* And so on and so forth */
    'app/components/**/*.{rb,js,html,haml}',
    'app/views/**/*.{rb,js,html,haml}',
  ]
}
Gem Name Must Match

bundle show loco_motion-rails outputs nothing if the gem is not installed or the name is wrong, which makes Tailwind silently skip every component class. Make sure the gem name matches exactly.

4. Icons

LocoMotion bundles a small set of icons, so common component icons render with no setup. Every component that accepts an icon: (and left_icon: / right_icon:) renders through the loco_icon engine:

= daisy_button("Save", icon: "check")
= daisy_alert(icon: "information-circle", css: "alert-info")

That built-in set covers LocoMotion's own chrome plus the standard alert icons. To render the full Heroicons set — including names like academic-cap — or to use another library, sync it into your app once. This applies to standalone loco_icon calls and to component icon: options:

bin/rails loco_motion:icons:add heroicons

Then reference any synced icon by name:

= loco_icon("academic-cap")
= loco_icon("bolt/solid")

LocoMotion can sync many libraries — Lucide, Phosphor, Tabler, Boxicons, Feather, and more. Add whichever ones you want and name them with a library:name token:

bin/rails loco_motion:icons:add lucide phosphor
bin/rails loco_motion:icons:list
= loco_icon("lucide:heart")
= loco_icon("phosphor:heart/duotone")

Synced SVGs land in your app's app/assets/svg/icons/<library> — commit them to your repository. Prefer a different library by default? Set it once in an initializer (and pick a matching variant, or nil for a flat library):

# config/initializers/loco_motion.rb
LocoMotion.configure do |config|
  config.default_icon_library = :lucide
  config.default_icon_variant = nil
end
Git is required to sync

loco_motion:icons:add fetches icon sets with git, so make sure it's available wherever you run the task.

See the Icons docs for the full rendering API.

Optional Keep your vendored set lean (treeshaking)

loco_motion:icons:add vendors a whole library. For larger apps you can instead vendor only the icons you actually use, the way Tailwind ships only the classes you reference. Download the full libraries once into a local cache (gitignored), then treeshake into your committed icons:

bin/rails loco_motion:icons:cache
bin/rails loco_motion:icons:sync

cache downloads the full libraries you reference into tmp/loco_motion/icons (gitignored). sync then scans your app — and config.icon_safelist — for icon tokens and copies only those from the cache into app/assets/svg/icons, so the committed set stays small while the full libraries live on your machine. sync auto-downloads any referenced library that isn't cached yet, so the first run just works.

Dynamically-named icons the scan can't see (e.g. a name built from a variable, loco_icon(icon_name)) won't be vendored — list those in config.icon_safelist as [library:]name[/variant] tokens.

In development you don't have to re-run sync as you work. Once the cache is populated, any icon in it renders on the next page refresh — the renderer falls back to the cache in development only. sync then promotes the icons you actually used into the committed app/assets/svg/icons for test and production (which never read the cache), so wire it into a git pre-commit hook to keep the vendored set in step:

# .git/hooks/pre-commit
#!/bin/sh
bin/rails loco_motion:icons:sync && git add app/assets/svg/icons

5. Try it out!

You're ready to start using the components! 🥳

Hop into one of your views and show the world what you can build.

Preview
Task completed!

Loading endless possibilities...

Code
= daisy_button("Done!", html: { onclick: "alert('Go you!')" })

= daisy_badge(css: "badge-success") do
  = loco_icon("check-circle")
  Task completed!

= daisy_alert(icon: "information-circle", css: "alert-info alert-soft") do
  Where will you go from here?

%p.flex.flex-col.gap-1.text-primary
  Loading endless possibilities...
  = daisy_progress(css: "w-full max-w-100 progress-primary")
Made with by Profoundry .
Copyright © 2023-2026 Profoundry LLC.
All rights reserved.