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.
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"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.
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.
LocoMotion uses Tailwind 4, which configures plugins directly in your CSS
file rather than in tailwind.config.js.
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";@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.
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}',
]
}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.
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
endloco_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.
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/iconsYou're ready to start using the components! 🥳
Hop into one of your views and show the world what you can build.
Loading endless possibilities...
Loading endless possibilities...
= 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")
Create virtual credit / debit cards to keep your real info safe.
Get $5 when you sign up — free to start!
Everything you need to grow your business with confidence!
CRM, Lead Generation, Project Management, Contracts, Online Payments, and more!
The ads above are affiliate links to products I regularly use and highly
recommend.
I may receive a commission if you decide to purchase.