SingleFile lets you build and serve beautiful, fast web apps from a single file.
Add a route
get '/' do
erb 'home'
end
Start the server
ruby singlefile.rb
What you don't need to build beautiful, fast web apps: A heavy framework or meta framework. Knowledge of complicated, esoteric framework-dependent patterns. Lots of javascript. A build step.
What you need to build beautiful, fast web apps: Design defaults and consistency for common frontend UI elements. Established patterns for keeping a handle on your CSS, patterns for simple and complex frontend interactivity, a way to interact with your database (optional), a simple way to handle routing & templating.
Ok - so we've taken some liberties with the term "Single File" - the full repository does in fact contain more than one file. What we're actually aiming for is...
You will need ruby installed on your system. If you don't already have it, we recommend using rbenv for this.
brew install rbenv
rbenv install 3.2.0
With ruby 3.2.0 installed, start the server with:
ruby singlefile.rb
Make It Yours
favicon.ico in the /assets/icons folder/helpers.rbstyles.css:root {
--ui-font-family:
--ui-font-family-headings:
}
Add a new route
Create a new file at /views/home.erb then add a route for it.
get '/' do
erb 'home'
end
Add A Link
<a href="/about">About</a>
Include a partial
<%%= erb :"partials/_sidebar" %>
Styling With Tailwind
Use any of the 50,000+ Tailwind classes available through Litewind. We recommend using Tailwind primarily for layout.
<div class="flex justify-center">
</div>
Styling Forms
Add .ui-form to any form to make all of the input elements inside of it look beautiful and consistent. (Read More & Demos).
<form class="ui-form"></form>
Styling Prose
Wrap the .ui-styled-text class around any html designed to render articles, markdown, or prose. (Read More & Demos).
<div class="ui-styled-text">
<%%= markdown_file_to_html("readme.md") %>
</div>
Buttons
Add the .ui-button class to any <button> or <a> element. (Read More & Demos).
<button class="ui-button --solid">Solid</button>
<button class="ui-button">Normal</button>
<button class="ui-button --minimal">Minimal</button>
Boxes
Use the .ui-box class to create a section of elevated content. (Read More & Demos).
<div class="ui-box"></div>
Titles
Add the .ui-title class to any title, or create a .ui-titlepair for a title with a tagline. (Read More & Demos).
<h3 class="ui-title">Coffee</h3>
<div class="ui-titlepair">
<h3 class="--title">Coffee</h3>
<p class="--description">Size: XL</p>
</div>
Chips
Display a list of tags using the .ui-chip class. (Read More & Demos).
<div class="ui-chip --sm --green">Default</div>
<div class="ui-chip --sm --blue">Solid</div>
<div class="ui-chip --sm --purple">Minimal</div>
Tooltips
Add an aria-label to an element alongside the .ui-simple-tooltip class to get clean animated plain text tooltips.
Read More & Demos).
<a href="#" class="ui-simple-tooltip --top" aria-label="Use the ui-simple-tooltip --top class">
Tooltip Top
</a>
Dropdowns
Use popover elements wrapped in a .ui-dropdown to create customizable, styled dropdowns. (Read More about Base Styles Dropdowns).
<div class="ui-dropdown">
<button class="--trigger" popovertarget="dropdown-content">
Click To Open
</button>
<div class="--drawer --bottom p-1 " id="dropdown-content" popover>
<a class="ui-button --minimal">
Google.com
</a>
</div>
</div>
Modal Dialogs
Use commandfor and add the .ui-modal class to your dialog element. (Read More about Base Styles modals).
<button commandfor="confirm-dialog" command="show-modal">
Delete Record
</button>
<dialog class="ui-modal" id="confirm-dialog" closedby="any" >
<p>Are you sure? This action cannot be undone</p>
<button class="ui-button">Continue</button>
</dialog>
Right Drawer
Use a dialog element with the .ui-modal --drawer class to create a right drawer. (Read More about Base Styles drawers).
Make an element dynamic, define some simple state, and render the value
<ui-state message="Hello World!">
<div ui-text="message"></div>
</ui-state>
Use javascript to update state from user interaction
<ui-state count="0">
<button ui-click="count++">Increment</button>
<div ui-text="count"></div>
</ui-state>
React to changes in input values
Use this.value to get the current value of the form input, and ui-change to listen for changes.
<ui-state name="">
<input type="text" ui-change="name = this.value" />
<span ui-text="name"></span>
</ui-state>
More Mini
first-name becomes firstName)Pass the component property to the <ui-state> tag to tell it which component to use, and pass in starting state where applicable.
<ui-state component="counter" count="25">
</ui-state>
Use mini to create the component and manage state. Then use preact standalone to render it.
import { html, render } from 'https://esm.sh/htm/preact/standalone';
MiniJS.register('counter', {
init() {
this.paint();
},
paint() {
const Counter = () => html`
<div>Content Here</div>
`
render(Counter(), this)
}
})
import { html, render } from 'https://esm.sh/htm/preact/standalone';
MiniJS.register('counter', {
init() {
this.paint();
},
paint() {
const Counter = () => html`
<div ui-text="count"></div>
<button ui-click="count++">+</button>
`
render(Counter(), this)
}
})
<ui-state component="counter">
<input ref="primary-input" type="text">
</ui-state>
MiniJS.register('counter', {
init() {
console.log(this.refs['primary-input'].value)
}
})
bundle installruby singlefile.rbIsn't this just Sinatra?
Sort of, yes. The codebase uses Sinatra, which is designed to be a single file framework. We use it as a shell to provide some simple patterns (routing, templating) and act as glue to bring together several other frameworks that we've built. If you'd like to port this to your language of choice, we'd love to feature you.
How did you decide on what libraries to include?
We've spent a lot of time thinking about patterns for working effectively with HTML, in a way that goes with the grain of the web, and isn't tighly coupled to any particular framework. Base Styles was built to solve our own problems internally (effortless style and consistency), and Mini Js was similar. Both have been refined in production for 3 years across dozens of projects and codebases. We've evolved our approach and syntax over time and landed somewhere that we think is a good balance between simplicity and power.