Packages
linea_ui
0.1.0
0.22.0
0.21.0
0.20.1
0.20.0
0.19.0
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.0
0.15.3
0.15.2
0.15.1
0.15.0
0.14.0
0.13.0
0.12.0
0.11.1
0.11.0
0.10.0
0.9.1
0.9.0
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.1.1
0.1.0
Phoenix LiveView components with the LineOne design.
Current section
Files
Jump to
Current section
Files
README.md
# LineaUI
Phoenix LiveView components with the LineOne design (Tailwind CSS v4), used the way Fluxon is
used: `use LineaUI`, one function per component, `class` that overrides the defaults, overlays
opened by id, and hooks that survive LiveView patches. No JavaScript dependencies.
> **License.** The code of this package is DevAction's, but the visual design it reproduces derives from
> [LineOne](https://themeforest.net/search/lineone) by PiniaStudio (ThemeForest). To use LineaUI you need a valid
> LineOne license that covers your project. See [LICENSE](LICENSE).
## Installation
```elixir
# mix.exs
{:linea_ui, "~> 0.1"}
```
```css
/* assets/css/app.css — remove daisyUI and its theme plugins */
@import "tailwindcss";
@import "../../deps/linea_ui/assets/css/fonts.css";
@import "../../deps/linea_ui/assets/css/linea_ui.css";
```
```js
// assets/js/app.js
import {Hooks as LineaHooks} from "linea_ui"
const liveSocket = new LiveSocket("/live", Socket, {
hooks: {...colocatedHooks, ...LineaHooks},
params: {_csrf_token: csrfToken},
})
```
```elixir
# lib/my_app_web/endpoint.ex — serves the Poppins and Inter fonts
plug Plug.Static, at: "/linea_ui", from: {:linea_ui, "priv/static"}
# lib/my_app_web.ex, inside html_helpers/0
use LineaUI, avoid_conflicts: true # keeps CoreComponents' button/input/table/label/error/header
```
Dark mode is LineOne's: the `dark` class on `<html>`, remembered in `localStorage["dark-mode"]`.
To avoid a flash, put this in the root layout's `<head>`:
```html
<script>localStorage.getItem("dark-mode") === "dark" && document.documentElement.classList.add("dark")</script>
```
Errors are translated with the app's own function:
```elixir
config :linea_ui, :translate_error, &MyAppWeb.CoreComponents.translate_error/1
```
## Icons
The small glyphs the components draw themselves (the chevron of a select, the cross of a toast, the
check of a checkbox, the arrows of a pager, the tools of the text editor...) are LineOne's own SVGs
by default. An app can have them drawn with [Hugeicons](https://hugeicons.com) (the free Stroke
Rounded set) instead:
```elixir
# mix.exs: the SVGs are vendored, not compiled
{:hugeicons, github: "hugeicons/hugeicons", ref: "3365154e0ae2461fbfb6249b89649127207a4f9e",
sparse: "icons", app: false, compile: false, depth: 1}
# config/config.exs
config :linea_ui, :icons, :hugeicons # :lineone is the default
```
```css
/* assets/css/app.css, after the linea_ui.css import: turns every huge-* class into a CSS mask */
@plugin "../../deps/linea_ui/assets/css/hugeicons.cjs" {
dir: "deps/hugeicons/icons";
}
```
`dir` is the folder of the SVGs, relative to where Tailwind runs (the app's root). The setting is read when
the page renders; the components whose hooks draw glyphs in the browser (toast, date picker) get it from a
`data-icons` attribute. Restart the app after changing it. Glyphs that are artwork (the two-tone icons of the
sidebar, the arrows of tooltips and popovers, the spinner) and the carets and check marks LineOne bakes into
its CSS (native select, tom-select, checkbox, radio, switch, table sorting) stay LineOne's in both sets.
For your own icons use `<.icon name="huge-search-01" class="size-5" />` (any other class name, such as
`fa-solid fa-house`, passes through). Write the name in full in the source, never interpolated: Tailwind
only sees what is written, and a name that is not in the set renders nothing.
## Claude Code skill
```bash
mix linea_ui.install # after adding the dependency, and after every upgrade
```
writes `.claude/skills/linea-ui/` — a short `SKILL.md` index (the rules every component shares
and one line per component) and `references/<component>.md` with the attrs, allowed values,
slots and examples of each, generated from the installed components themselves, so it never
drifts from the code. Only the index is loaded up front; a reference is read when its component
is used. The task also points `AGENTS.md`/`CLAUDE.md` at the skill (once) and prints the
remaining setup lines. `mix linea_ui.gen.skill --path <dir>` writes only the skill.
## Usage
```heex
<.button color="primary" phx-click={LineaUI.open("new-order")}>New order</.button>
<.modal id="new-order">
<.form for={@form} phx-submit="save">
<.input field={@form[:customer]} label="Customer" />
<.button type="submit" color="primary">Save</.button>
</.form>
</.modal>
```
```elixir
{:noreply, LineaUI.close(socket, "new-order")}
```
## Development
```bash
mix test && mix js.test # package
cd preview && LINEA_PREVIEW_PORT=4200 mix phx.server
```
`preview/` is a Phoenix app with one page per LineOne demo page; the original LineOne page is
served next to it at `/lineone/<page>.html`. `node scripts/shot.mjs` takes the screenshots used to
compare them. `CONVENTIONS.md` is the guide for writing components.