Tableau
View SourceIntroduction
You add Corex to a Tableau static site: HEEx templates, Esbuild, Tailwind, and lazy Corex hooks over LiveSocket. When you finish, you can render Corex components on static pages.
Optional follow-ups: Tableau Theming, Tableau Mode, Tableau Localize. For Phoenix apps with cookies and plugs, see Dark mode, Theming, and Localize.
Reference templates: corex-ui/soonex and corex-ui/soonex_i18n.
Before you start
| Requirement | Notes |
|---|---|
| Elixir ~> 1.15 | Match your Tableau version |
| Node.js | For Esbuild and npm deps |
mix tableau.new | HEEx + Esbuild + Tailwind template |
How it works
mix corex.designcopies token and component CSS intoassets/corex/.- Esbuild bundles
assets/js/site.jsas ESM with splitting into_site/js/. RootLayoutloads CSRF meta,site.css, andtype="module"forsite.js.LiveSocketregisters only the Corex hooks you import (lazy factories keep chunks small).
Create the site
mix tableau.new my_site --template heex --js esbuild --css tailwind
cd my_site
Add Corex to mix.exs:
{:corex, "~> 0.1.0-rc.0"}mix deps.get
mix corex.design
Configure Esbuild in config/config.exs (ESM + splitting for dynamic hook imports):
config :esbuild,
version: "0.25.5",
default: [
args:
~w(js/site.js --bundle --format=esm --splitting --target=es2022 --outdir=../_site/js),
cd: Path.expand("../assets", __DIR__),
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
]Import Corex CSS in assets/css/site.css:
@import "tailwindcss";
@import "../corex/main.css";
@import "../corex/theme/neo.css";
@import "../corex/components/typo.css";
@import "../corex/components/layout.css";
@import "../corex/components/accordion.css";Add typo and layout classes on <body> in your root layout (see below).
Hooks lazy
Import only the hooks you use. In assets/js/site.js:
import { Socket } from "phoenix"
import { LiveSocket } from "phoenix_live_view"
import { hooks } from "corex/hooks"
const csrfToken = document
.querySelector("meta[name='csrf-token']")
?.getAttribute("content")
const liveSocket = new LiveSocket("/live", Socket, {
longPollFallbackMs: 2500,
params: { _csrf_token: csrfToken },
hooks: {
...hooks({
Accordion: () => import("corex/accordion"),
}),
},
})
liveSocket.connect()Hooks all
Load every Corex hook in one bundle:
import { Socket } from "phoenix"
import { LiveSocket } from "phoenix_live_view"
import corex from "corex"
const csrfToken = document
.querySelector("meta[name='csrf-token']")
?.getAttribute("content")
const liveSocket = new LiveSocket("/live", Socket, {
longPollFallbackMs: 2500,
params: { _csrf_token: csrfToken },
hooks: { ...corex },
})
liveSocket.connect()Root layout
Corex expects ESM and a CSRF token. In lib/layouts/root_layout.ex:
defmodule MyApp.RootLayout do
import Phoenix.Controller, only: [get_csrf_token: 0]
use Tableau.Layout
use Phoenix.Component
use Corex
def template(assigns) do
~H"""
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="csrf-token" content={get_csrf_token()} />
<title>{assigns[:page_title] || "MyApp"}</title>
<link rel="stylesheet" href="/css/site.css" />
<script type="module" src="/js/site.js" />
</head>
<body class="layout typo">
<main class="layout__main">
<div class="layout__content">
{render(@inner_content)}
</div>
</main>
</body>
</html>
"""
|> Phoenix.HTML.Safe.to_iodata()
end
endTry a component
In a page template:
<.accordion
id="welcome-accordion"
class="accordion"
items={Corex.Content.new([
[value: "first", label: "First", content: "Panel one."],
[value: "second", label: "Second", content: "Panel two."],
[value: "third", label: "Third", content: "Panel three."]
])}
/>Optional: MCP on Bandit
Tableau has no Phoenix endpoint. Run MCP on a separate Bandit port (default 4004).
lib/my_app/mcp_plug.ex:
defmodule MyApp.McpPlug do
use Plug.Builder
plug Corex.MCP
plug :not_found
defp not_found(conn, _) do
if conn.halted? do
conn
else
conn
|> Plug.Conn.put_resp_content_type("text/plain")
|> Plug.Conn.send_resp(404, "Not found")
end
end
endlib/my_app/application.ex:
defmodule MyApp.Application do
use Application
@impl true
def start(_type, _args) do
children =
if Application.get_env(:my_app, :mcp_enabled, false) do
[
{Bandit,
plug: MyApp.McpPlug,
scheme: :http,
port: Application.get_env(:my_app, :mcp_port, 4004)}
]
else
[]
end
Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
end
endconfig/dev.exs:
config :my_app, :mcp_enabled, true
config :my_app, :mcp_port, 4004Point your MCP client at http://localhost:4004.
Related
- Tableau Theming, Tableau Mode, Tableau Localize
- Manual installation — Esbuild,
mix corex.design,use Corex - MCP — plug behavior and Phoenix endpoint setup
- Installation —
mix corex.newfor full Phoenix apps