@trap_stevo/axis
v0.0.2
Published
Universal interface intelligence engine unifying real environment signals into a single immutable snapshot for explicit, predictable layout decisions across every screen.
Maintainers
Keywords
Readme
🧭 @trap_stevo/axis
Universal Interface Intelligence Engine
Every device. Every shape. One layout mind.
A deterministic, framework-agnostic interface intelligence engine,
unifying capability detection, viewport reality, layout scale, aspect awareness, safe-area constraints, ergonomic rules, grid systems, typography signals, UI runtime helpers, profile groups, and resolver-driven values into a single immutable snapshot — enabling explicit, predictable, and reusable layout decisions across every screen and input context.
Axis does not render UI.
Axis does not replace CSS.
Axis supplies facts so layout logic stays explicit, predictable, and reusable.
Why Axis Exists
Interfaces fail for repeatable reasons:
- breakpoint sprawl
- duplicated layouts across form factors
- touch vs pointer conflicts
- hybrid laptops and edge devices
- gradual layout drift
Viewport width alone cannot describe interaction.
Device detection collapses under hybrids.
Axis measures reality, derives intent-ready signals, and normalizes outcomes.
What Axis Covers — and What It Leaves Alone
Axis covers
- interface intelligence
- deterministic snapshots
- capability-driven decisions
- continuous scaling
- ergonomic normalization
- safe-area and viewport reality
- profile identities and profile groups
- UI runtime helpers derived from the snapshot
- resolver-driven values for app-specific configuration
- framework-agnostic consumption
Axis leaves alone
- rendering
- CSS authoring
- design systems
- component libraries
Axis answers what the environment looks like.
Your code answers what to do about it.
✨ Feature Overview
🧠 Capabilities
- touch / pointer / hybrid detection
- hover and coarse/fine pointer awareness
- touch-laptop recognition
- max touch points signal
📐 Space & Aspect
- orientation awareness
- aspect ratio classification
- shape detection: tall / standard / wide / ultraWide / squareish
🪜 Scaling
- continuous scale factor
- rem / px conversion (raw + CSS-ready)
- spacing, radius, shadow, icon, control-height, and width helpers
- explicit conversions: px→rem and rem→px
🧩 Layout
- stack vs split layout mode
- container math
- grid math
- nested layouts
🎯 Ergonomics
- minimum hit targets
- control height normalization
- hybrid input handling
- tier normalization (pointer compact windows normalize to comfortable)
🔤 Typography
- fluid type ramp
- line-height presets
- readable line-length helpers
- clamp helpers
🧷 Reality Handling
- safe-area insets (CSS + resolved px)
- visual viewport tracking
- mobile keyboard heuristics
- viewport unit resolution (svh/dvh/lvh)
🧭 Identity & Grouping
- canonical profile IDs
- group-based matching (mobileLike, desktopLike, keyboardOpen, etc.)
- profile-based matching and declarative branching
🧩 UI Runtime Helpers
- UI scale mode (auto/compact/comfortable/large)
- UI rem/px helpers
- UI spacing, radius, hit target helpers
🧷 Resolver Values
- optional resolver-driven values via
resolve()andresolveString()
🔄 Integration
- reactive subscriptions
- RAF-coalesced updates
- SSR-safe fallback snapshot
🖥️ System Requirements
| Category | Requirement |
|--------|-------------|
| Runtime | ES2020+, ESM |
| Environment | Browser or Node.js |
| Browser Support | Chrome / Edge (latest), Firefox (latest), Safari 16+, iOS Safari 16+ |
| SSR Support | Yes — stable fallback snapshot when window remains unavailable |
| Input Types | Touch, pointer, hybrid |
| Viewport APIs | Uses visualViewport when available; graceful fallback otherwise |
| Safe Area APIs | Uses CSS env(safe-area-inset-*) when supported |
| Device Coverage | Phones, tablets, desktops, hybrid laptops, ultra-wide displays |
| Dependencies | None |
The Mental Model
Everything flows from a snapshot.
- snapshot creation follows environmental change
- snapshot contents describe the full interface environment
- snapshot immutability prevents drift
- one subscription supports unlimited reuse
Query once.
Reuse everywhere.
Core Signals
Orientation
axis.orientation; // "portrait" | "landscape"Input
axis.input; // "touch" | "pointer" | "hybrid"Tier
axis.tier; // "compact" | "comfortable" | "expanded" | "cinema"
axis.tierRaw; // raw tier before normalizationAspect & Shape
axis.aspect.ratio;
axis.aspect.shape;Profile Identity
axis.profile.id; // TouchCompactTall📘 Axis Type Reference
AxisEngine
| Member | Type | Description | |---|---|---| | snapshot() | () => AxisSnapshot | Returns current immutable snapshot | | subscribe(handler) | (axis:AxisSnapshot) => () => void | Subscribes to snapshot updates | | setProfileOverride(id) | (AxisProfileID | null) => void | Forces a profile identity | | getProfileOverride() | () => AxisProfileID | null | Reads override state | | setResolver(source) | (AxisResolveSource | null) => void | Installs a resolver source | | getResolver() | () => AxisResolveSource | null | Reads resolver source | | destroy() | () => void | Removes listeners and subscriptions |
AxisSnapshot
| Field | Type | Description | |---|---|---| | caps | AxisCaps | Capability signals | | orientation | "portrait" | "landscape" | Screen orientation | | viewportWidth | number | Layout viewport width | | viewportHeight | number | Layout viewport height | | shortSide | number | Shortest viewport edge | | input | "touch" | "pointer" | "hybrid" | Derived input mode | | tierRaw | "compact" | "comfortable" | "expanded" | "cinema" | Raw tier (pre-normalization) | | tier | "compact" | "comfortable" | "expanded" | "cinema" | Normalized tier | | aspect | AxisAspect | Ratio + shape helpers | | layoutMode | "stack" | "split" | Primary layout mode | | profile | AxisProfile | Canonical identity | | scale | AxisScale | Measurement helpers | | viewport | AxisViewport | Layout + visual viewport reality | | safeArea | AxisSafeArea | Safe-area insets | | insets | AxisInsets | Content padding and gutters | | controls | AxisControls | Ergonomic sizing helpers | | density | AxisDensity | Density factor + override | | container | AxisContainer | Container sizing helpers | | grid | AxisGrid | Grid helpers | | type | AxisType | Typography helpers | | ui | AxisUI | UI runtime helpers derived from the snapshot | | resolve(key) | (key:string) => AxisResolveValue | Resolver-driven value | | resolveString(key,fallback) | (key:string,fallback:string)=>string | Resolver string helper | | isProfile(profiles) | (AxisProfileID | AxisProfileID[]) => boolean | Profile identity check | | only(profiles,fn) | (AxisProfileID | AxisProfileID[], fn:()=>void)=>void | Execute for matching profiles | | unless(profiles,fn) | (AxisProfileID | AxisProfileID[], fn:()=>void)=>void | Execute for non-matching profiles | | onlyIf(predicate,fn) | (predicate:(axis)=>boolean, fn:()=>void)=>void | Predicate-based execution | | match(cases,fallback) | (Array<[AxisProfileID|AxisProfileID[],T]>,T)=>T | Declarative profile matching | | isGroup(groups) | (AxisProfileGroupID | AxisProfileGroupID[]) => boolean | Group membership check | | onlyGroup(groups,fn) | (AxisProfileGroupID | AxisProfileGroupID[], fn:()=>void)=>void | Execute for matching groups | | unlessGroup(groups,fn) | (AxisProfileGroupID | AxisProfileGroupID[], fn:()=>void)=>void | Execute for non-matching groups | | matchGroup(cases,fallback) | (Array<[AxisProfileGroupID|AxisProfileGroupID[],T]>,T)=>T | Declarative group matching | | isTierRaw(tiers) | (AxisTier | AxisTier[]) => boolean | Raw tier check | | isTier(tiers) | (AxisTier | AxisTier[]) => boolean | Normalized tier check | | applyGlobalScaleVar(name?) | (cssVarName?:string)=>void | Writes scale factor to a CSS variable |
AxisCaps
| Field | Type | Meaning | |---|---|---| | touch | boolean | Touch-capable input detected | | hover | boolean | Hover-capable pointer detected | | anyPointer | "none" | "coarse" | "fine" | Highest fidelity pointer | | anyHover | "none" | "hover" | Hover support | | maxTouchPoints | number | Touch contact capacity | | isTouchLaptop | boolean | Touch + hover + fine pointer | | isHybrid | boolean | Hybrid input classification |
AxisAspect
| Field | Type | Description | |---|---|---| | ratio | number | Width / height | | shape | AxisShape | Shape classification | | isPortraitLike | boolean | Portrait-biased ratio | | isLandscapeLike | boolean | Landscape-biased ratio | | isSquareish | boolean | Near-square ratio | | isTall | boolean | Tall layout | | isWide | boolean | Wide layout | | isUltraWide | boolean | Ultra-wide layout | | pick(map) | (map) => number | Shape-based selector |
AxisProfile
| Field | Type | Description | |---|---|---| | id | AxisProfileID | Canonical profile string | | input | AxisInput | Input classification | | tier | AxisTier | Layout tier | | shape | AxisShape | Aspect shape | | orientation | AxisOrientation | Orientation | | isTouch | boolean | Touch input | | isPointer | boolean | Pointer input | | isHybrid | boolean | Hybrid input | | isCompact | boolean | Compact tier | | isComfortable | boolean | Comfortable tier | | isExpanded | boolean | Expanded tier | | isCinema | boolean | Cinema tier | | isTall | boolean | Tall shape | | isStandard | boolean | Standard shape | | isWide | boolean | Wide shape | | isUltraWide | boolean | Ultra-wide shape | | isSquareish | boolean | Squareish shape | | isPortrait | boolean | Portrait orientation | | isLandscape | boolean | Landscape orientation |
🧭 Profile Matrix (Canonical Identities)
Profile IDs exist for identity and grouping—not conditional logic by default.
Profiles follow:
<Input><Tier><Shape>Total combinations: 60
Normalization rule: pointer + compact short-side windows normalize to comfortable.
| Input | Tier | Shape | Profile ID | |---|---|---|---| | Touch | Compact | Tall | TouchCompactTall | | Touch | Compact | Standard | TouchCompactStandard | | Touch | Compact | Wide | TouchCompactWide | | Touch | Compact | UltraWide | TouchCompactUltraWide | | Touch | Compact | Squareish | TouchCompactSquareish | | Touch | Comfortable | Tall | TouchComfortableTall | | Touch | Comfortable | Standard | TouchComfortableStandard | | Touch | Comfortable | Wide | TouchComfortableWide | | Touch | Comfortable | UltraWide | TouchComfortableUltraWide | | Touch | Comfortable | Squareish | TouchComfortableSquareish | | Touch | Expanded | Tall | TouchExpandedTall | | Touch | Expanded | Standard | TouchExpandedStandard | | Touch | Expanded | Wide | TouchExpandedWide | | Touch | Expanded | UltraWide | TouchExpandedUltraWide | | Touch | Expanded | Squareish | TouchExpandedSquareish | | Touch | Cinema | Tall | TouchCinemaTall | | Touch | Cinema | Standard | TouchCinemaStandard | | Touch | Cinema | Wide | TouchCinemaWide | | Touch | Cinema | UltraWide | TouchCinemaUltraWide | | Touch | Cinema | Squareish | TouchCinemaSquareish | | Pointer | Comfortable | Tall | PointerComfortableTall | | Pointer | Comfortable | Standard | PointerComfortableStandard | | Pointer | Comfortable | Wide | PointerComfortableWide | | Pointer | Comfortable | UltraWide | PointerComfortableUltraWide | | Pointer | Comfortable | Squareish | PointerComfortableSquareish | | Pointer | Expanded | Tall | PointerExpandedTall | | Pointer | Expanded | Standard | PointerExpandedStandard | | Pointer | Expanded | Wide | PointerExpandedWide | | Pointer | Expanded | UltraWide | PointerExpandedUltraWide | | Pointer | Expanded | Squareish | PointerExpandedSquareish | | Pointer | Cinema | Tall | PointerCinemaTall | | Pointer | Cinema | Standard | PointerCinemaStandard | | Pointer | Cinema | Wide | PointerCinemaWide | | Pointer | Cinema | UltraWide | PointerCinemaUltraWide | | Pointer | Cinema | Squareish | PointerCinemaSquareish | | Hybrid | Compact | Tall | HybridCompactTall | | Hybrid | Compact | Standard | HybridCompactStandard | | Hybrid | Compact | Wide | HybridCompactWide | | Hybrid | Compact | UltraWide | HybridCompactUltraWide | | Hybrid | Compact | Squareish | HybridCompactSquareish | | Hybrid | Comfortable | Tall | HybridComfortableTall | | Hybrid | Comfortable | Standard | HybridComfortableStandard | | Hybrid | Comfortable | Wide | HybridComfortableWide | | Hybrid | Comfortable | UltraWide | HybridComfortableUltraWide | | Hybrid | Comfortable | Squareish | HybridComfortableSquareish | | Hybrid | Expanded | Tall | HybridExpandedTall | | Hybrid | Expanded | Standard | HybridExpandedStandard | | Hybrid | Expanded | Wide | HybridExpandedWide | | Hybrid | Expanded | UltraWide | HybridExpandedUltraWide | | Hybrid | Expanded | Squareish | HybridExpandedSquareish | | Hybrid | Cinema | Tall | HybridCinemaTall | | Hybrid | Cinema | Standard | HybridCinemaStandard | | Hybrid | Cinema | Wide | HybridCinemaWide | | Hybrid | Cinema | UltraWide | HybridCinemaUltraWide | | Hybrid | Cinema | Squareish | HybridCinemaSquareish |
📏 AxisScale Reference
Axis exposes scaled numeric values and CSS-ready strings only.
No additional unit systems exist.
uiRem and uiPx act as semantic aliases.
Unit conversion stays explicit by design to prevent silent layout drift.
| Helper | Params | Returns | Purpose | |---|---:|---:|---| | factor | — | number | Global scale multiplier | | rem | (n:number) | string | Scaled rem | | px | (n:number) | number | Scaled pixels | | remRaw | (n:number) | number | Scaled rem (raw number) | | pxRaw | (n:number) | number | Scaled px (raw number) | | pxToRemRaw | (px:number) | number | Convert px to rem (raw) | | pxToRem | (px:number) | string | Convert px to rem | | remToPxRaw | (rem:number) | number | Convert rem to px (raw) | | remToPx | (rem:number) | number | Convert rem to px | | uiRem | (n:number) | string | Alias of rem | | uiPx | (n:number) | number | Alias of px | | clampRem | (min,ideal,max) | string | CSS clamp | | space | (step:number) | string | Spacing rhythm | | size | (step:number) | string | General sizing | | inset | (t,r,b,l) | string | Padding shorthand | | insetXY | (x,y) | string | Axis padding | | radius | (key) | string | Border radius | | border | (key) | string | Border width | | stroke | (key) | number | Stroke width | | shadow | (key) | string | Elevation | | blur | (key) | string | Blur radius | | font | (key) | string | Font size | | line | (key) | string | Line height | | tracking | (key) | string | Letter spacing | | weight | (key) | number | Font weight | | icon | (key) | number | Icon size | | controlHeight | (key) | number | Control height | | width | (key) | string | Width presets |
🧩 Essentials Reference
Essentials extend the snapshot surface area without changing core behavior. Axis core stays identical; Essentials add derived convenience fields.
Included fields:
- viewport
- safeArea
- insets
- controls
- density
- container
- grid
- type
🔌 Axis UI Runtime Reference
Axis UI derives from the snapshot and exposes on axis.ui.
AxisUI
| Member | Type | Description | |---|---|---| | mode | "auto" | "compact" | "comfortable" | "large" | Active UI mode | | factor | number | UI scale factor | | setMode(mode) | (mode:AxisUIMode | null) => void | Overrides UI mode | | getMode() | () => AxisUIMode | Reads active UI mode | | rem(value) | (value:number)=>string | UI rem helper | | px(value) | (value:number)=>number | UI px helper | | font(key) | (key:string)=>string | UI font helper | | space(step) | (step:number)=>string | UI spacing helper | | radius(key) | (key:string)=>string | UI radius helper | | hit() | ()=>number | UI minimum hit target | | hitRem() | ()=>string | UI minimum hit target (rem) |
🔌 Resolver Reference
Axis supports an optional resolver source installed on the engine.
Install a resolver
const engine = createAxisEngine();
engine.setResolver((axis, key) => {
if (key === "theme.primary")
{
return "#1A73E8";
}
return null;
});Resolve values
axis.resolve("theme.primary"); // string | number | boolean | null | object | array
axis.resolveString("theme.primary", "#000000"); // string🧭 Profile Groups Reference
Axis exposes ergonomic profile groups for higher-level matching.
Group checks
axis.isGroup("mobileLike");
axis.isGroup(["touch", "compact"]);
axis.isGroup("keyboardOpen");Group execution
axis.onlyGroup("mobileLike", () => {
// touch-first stacked UI
});
axis.unlessGroup("keyboardOpen", () => {
// normal bottom bar behavior
});Group matching
const columns = axis.matchGroup([
[["mobileLike"], 4],
[["desktopLike"], 12]
], 8);Built-in group IDs
touch, pointer, hybrid
compact, comfortable, expanded, cinema
tall, standard, wide, ultraWide, squareish
portrait, landscape
compactTouch, comfortableTouch, expandedTouch, cinemaTouch
compactPointer, comfortablePointer, expandedPointer, cinemaPointer
compactHybrid, comfortableHybrid, expandedHybrid, cinemaHybrid
mobileLike
desktopLike
keyboardOpen
🔀 Comparisons & Branching
Axis replaces conditional chaos with explicit, snapshot-driven branching.
Instead of guessing screen types or stacking breakpoints, logic reads directly from measured reality.
Snapshot Branching Reference
| Intent | Axis Signal | Use Instead of | |---|---|---| | Input class | axis.profile.isTouch | if (isMobile) | | Pointer precision | axis.caps.anyPointer | hover media queries | | Hybrid devices | axis.profile.isHybrid | device detection | | Density tier | axis.profile.isCompact | width breakpoints | | Layout structure | axis.layoutMode | grid breakpoint forks | | Aspect shape | axis.profile.isUltraWide | ratio math | | Orientation | axis.profile.isPortrait | orientation media queries | | Exact identity | axis.isProfile(...) | hard-coded layouts | | Keyboard open | axis.isGroup("keyboardOpen") | brittle keyboard listeners |
Profile Identity Comparison
Test one or more canonical profile IDs.
axis.isProfile("TouchCompactTall");Multiple profiles:
axis.isProfile([
"TouchCompactTall",
"HybridComfortableWide"
]);Input Class Checks (Touch / Pointer / Hybrid)
Axis exposes class-level booleans directly on the profile.
axis.profile.isTouch;
axis.profile.isPointer;
axis.profile.isHybrid;Typical usage:
if (axis.profile.isTouch)
{
// touch-first behavior
}Tier-Based Branching
Tier flags replace breakpoint math entirely.
axis.profile.isCompact;
axis.profile.isComfortable;
axis.profile.isExpanded;
axis.profile.isCinema;Example:
if (axis.profile.isCompact)
{
// dense, stacked UI
}Shape-Based Branching
Shape flags replace aspect-ratio thresholds.
axis.profile.isTall;
axis.profile.isStandard;
axis.profile.isWide;
axis.profile.isUltraWide;
axis.profile.isSquareish;Example:
if (axis.profile.isUltraWide)
{
// multi-column canvas layout
}Orientation Checks
axis.profile.isPortrait;
axis.profile.isLandscape;only — Execute for Matching Profiles
axis.only("TouchCompactTall", () => {
// executes only for this profile
});Multiple profiles:
axis.only([
"TouchCompactTall",
"HybridComfortableStandard"
], () => {
// executes for any matching profile
});unless — Execute for Non-Matching Profiles
axis.unless("PointerExpandedWide", () => {
// executes for all except this profile
});Predicate-Based Branching (onlyIf)
Use when logic depends on capability, not identity.
axis.onlyIf(a => a.caps.isTouchLaptop, () => {
// hybrid laptop ergonomics
});Declarative Matching (match)
Select values without branching trees.
const columns = axis.match([
[["TouchCompactTall"], 4],
[["TouchComfortableStandard"], 6],
[["PointerExpandedWide"], 12]
], 8);- first match wins
- fallback always required
- no cascading if logic
Capability-Based Branching
if (axis.caps.hover && axis.caps.anyPointer === "fine")
{
// hover-precise interactions
}if (axis.caps.isHybrid)
{
// dual-input ergonomics
}⚙️ Snapshot Helper Methods
| Helper | Purpose | |---|---| | isProfile | Profile identity check | | only | Execute for matching profiles | | unless | Execute for non-matching profiles | | onlyIf | Predicate-based execution | | match | Declarative value selection | | isGroup | Group membership check | | onlyGroup | Execute for matching groups | | unlessGroup | Execute for non-matching groups | | matchGroup | Declarative group selection | | isTierRaw | Raw tier comparison | | isTier | Normalized tier comparison | | applyGlobalScaleVar | Write scale factor to CSS variable |
🧾 Usage Patterns
One-Off Snapshot
const axis = createAxisEngine().snapshot();Reactive Subscription
const engine = createAxisEngine();
const unsubscribe = engine.subscribe(axis => {
document.body.dataset.profile = axis.profile.id;
});Conditional Logic
axis.only(["TouchCompactTall"], () => {});
axis.unless("PointerExpandedWide", () => {});Predicate Logic
axis.onlyIf(a => a.caps.isTouchLaptop, () => {});Declarative Matching
const density = axis.match([
[["TouchCompactTall"], 1.1],
[["PointerExpandedWide"], 0.95]
], 1.0);Group Matching
const layout = axis.matchGroup([
[["mobileLike"], "stack"],
[["desktopLike"], "split"]
], "split");Global Scale Variable
axis.applyGlobalScaleVar("--axis-scale");Profile Override
engine.setProfileOverride("TouchCompactTall");
engine.setProfileOverride(null);Resolver Usage
engine.setResolver((axis, key) => {
(void axis);
if (key === "layout.headerHeight")
{
return "64px";
}
return null;
});
const h = axis.resolveString("layout.headerHeight", "56px");SSR / Node
const axis = createAxisEngine().snapshot();Performance Notes
- RAF-coalesced updates
- minimal recomputation
- deterministic snapshot rebuild
- zero polling
- safe-area measurer reused
📦 Installation
npm install @trap_stevo/axis
# or
yarn add @trap_stevo/axis- ESM
- TypeScript included
- framework-agnostic
- no config
- no flags
- no CLI
⚡ Quick Start
Each Quick Start below renders the same UI, uses the same Axis signals, and makes the same layout decisions.
- Shared intent across all examples
- one Axis engine
- one subscription
- snapshot-driven layout
- no breakpoints
- no device detection
- Axis → decision → CSS
Quick Start — Teaser
import createAxisEngine from "@trap_stevo/axis";
const engine = createAxisEngine();
const axis = engine.snapshot();
console.log(axis.input);
console.log(axis.tier);
console.log(axis.aspect.shape);Shared Layout Decisions (All Examples)
Every Quick Start below uses these exact rules:
| Decision | Axis Signal | |---|---| | Layout structure | axis.layoutMode | | Grid columns | axis.grid.pickColumns(...) | | Page padding | axis.insets.contentPadding | | Typography | axis.type.size, axis.type.leading | | Controls | axis.controls.minHit() | | Identity | axis.profile.id | | UI sizing | axis.ui.space, axis.ui.hit |
Quick Start — React
import React, { useEffect, useMemo, useState } from "react";
import createAxisEngine from "@trap_stevo/axis";
export default function App()
{
const engine = useMemo(() => createAxisEngine(), []);
const [axis, setAxis] = useState(() => engine.snapshot());
useEffect(() => {
const unsubscribe = engine.subscribe(next => setAxis(next));
return () => {
unsubscribe();
engine.destroy();
};
}, [engine]);
const columns = axis.grid.pickColumns({
compact : 4,
comfortable : 8,
expanded : 12,
cinema : 12,
default : 8
});
return (
<div
style={{
minHeight : axis.viewport.h(100, "dynamic"),
background : axis.caps.touch ? "rgb(10,20,40)" : "rgb(12,12,14)",
color : "white",
display : "flex",
justifyContent : "center"
}}
>
<div
style={{
width : "100%",
maxWidth : axis.container.maxWidth,
padding : axis.insets.contentPadding,
boxSizing : "border-box"
}}
>
<header
style={{
display : "flex",
justifyContent : "space-between",
alignItems : "center",
marginBottom : axis.ui.space(8)
}}
>
<div>
<div
style={{
fontSize : axis.type.size("2xl"),
fontWeight : 700,
lineHeight : axis.type.leading("tight")
}}
>
Axis Quick Start
</div>
<div style={{ opacity : 0.75 }}>
profile : {axis.profile.id}
</div>
</div>
<button
style={{
minHeight : axis.ui.hit() + "px",
padding : axis.scale.insetXY(1.0, 0.75),
borderRadius : axis.ui.radius("xl"),
border : "none",
background : "#1A73E8",
color : "white",
cursor : "pointer"
}}
>
Action
</button>
</header>
<main
style={{
display : "grid",
gridTemplateColumns :
axis.layoutMode === "stack"
? "1fr"
: `repeat(${columns}, 1fr)`,
gap : axis.grid.gutter
}}
>
{Array.from({ length : columns }).map((_, i) => (
<div
key={String(i)}
style={{
padding : axis.container.cardPadding,
borderRadius : axis.scale.radius("2xl"),
background : "rgba(255,255,255,0.06)"
}}
>
Card {i + 1}
</div>
))}
</main>
</div>
</div>
);
}Quick Start — HTML / Vanilla DOM (Canonical Reference)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>Axis Quick Start</title>
</head>
<body>
<div id="root"></div>
<script type="module">
import createAxisEngine from "@trap_stevo/axis";
const engine = createAxisEngine();
const root = document.getElementById("root");
function render(axis)
{
const columns = axis.grid.pickColumns({
compact : 4,
comfortable : 8,
expanded : 12,
cinema : 12,
default : 8
});
root.innerHTML = `
<div style="
min-height:${axis.viewport.h(100, "dynamic")};
background:${axis.caps.touch ? "rgb(10,20,40)" : "rgb(12,12,14)"};
color:white;
display:flex;
justify-content:center;
">
<div style="
width:100%;
max-width:${axis.container.maxWidth};
padding:${axis.insets.contentPadding};
box-sizing:border-box;
">
<header style="
display:flex;
justify-content:space-between;
align-items:center;
margin-bottom:${axis.ui.space(8)};
">
<div>
<div style="
font-size:${axis.type.size("2xl")};
font-weight:700;
">
Axis Quick Start
</div>
<div style="opacity:0.75">
profile : ${axis.profile.id}
</div>
</div>
<button style="
min-height:${axis.ui.hit()}px;
padding:${axis.scale.insetXY(1.0,0.75)};
border-radius:${axis.ui.radius("xl")};
background:#1A73E8;
color:white;
border:none;
cursor:pointer;
">
Action
</button>
</header>
<main style="
display:grid;
grid-template-columns:${axis.layoutMode === "stack"
? "1fr"
: `repeat(${columns},1fr)`};
gap:${axis.grid.gutter};
">
${Array.from({ length : columns }).map((_, i) => `
<div style="
padding:${axis.container.cardPadding};
border-radius:${axis.scale.radius("2xl")};
background:rgba(255,255,255,0.06);
">
Card ${i + 1}
</div>
`).join("")}
</main>
</div>
</div>
`;
}
const unsubscribe = engine.subscribe(render);
window.addEventListener("beforeunload", () => {
unsubscribe();
engine.destroy();
});
</script>
</body>
</html>Quick Start — HTML / Vanilla DOM (Axis UI Reference)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>Axis Demo</title>
<style>
:root
{
font-family : system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
}
body
{
margin : 0;
}
.app
{
min-height : 100vh;
display : flex;
justify-content : center;
align-items : stretch;
}
.shell
{
width : 100%;
display : flex;
flex-direction : column;
box-sizing : border-box;
}
.row
{
display : flex;
align-items : center;
justify-content : space-between;
gap : 12px;
}
.card
{
box-sizing : border-box;
width : 100%;
}
.pill
{
display : inline-flex;
align-items : center;
gap : 10px;
padding : 8px 12px;
border-radius : 999px;
white-space : nowrap;
}
.grid
{
display : grid;
width : 100%;
box-sizing : border-box;
}
.btn
{
border : none;
cursor : pointer;
user-select : none;
}
.muted
{
opacity : 0.78;
}
.mono
{
font-family : ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="module">
import createAxisEngine from "../../dist/index.js";
const axis = createAxisEngine();
const root = document.getElementById("root");
axis.setResolver(function(axisSnapshot, key)
{
if (key === "bg")
{
return axisSnapshot.caps.touch
? "rgba(10, 20, 40, 1)"
: "rgba(12, 12, 14, 1)";
}
if (key === "card")
{
return "rgba(255, 255, 255, 0.06)";
}
if (key === "border")
{
return "rgba(255, 255, 255, 0.10)";
}
if (key === "text")
{
return "white";
}
if (key === "muted")
{
return "rgba(255, 255, 255, 0.78)";
}
if (key === "accent")
{
return "#1A73E8";
}
return null;
});
function el(tag, props, children)
{
const node = document.createElement(tag);
if (props)
{
for (const key of Object.keys(props))
{
if (key === "style")
{
Object.assign(node.style, props.style);
continue;
}
if (key.startsWith("on") && typeof props[key] === "function")
{
node.addEventListener(key.slice(2).toLowerCase(), props[key]);
continue;
}
node.setAttribute(key, String(props[key]));
}
}
if (Array.isArray(children))
{
for (const child of children)
{
if (typeof child === "string")
{
node.appendChild(document.createTextNode(child));
}
else if (child)
{
node.appendChild(child);
}
}
}
return node;
}
function render(snapshot)
{
const ui = snapshot.ui;
const bg = snapshot.resolveString("bg", "rgba(12, 12, 14, 1)");
const cardBg = snapshot.resolveString("card", "rgba(255, 255, 255, 0.06)");
const border = snapshot.resolveString("border", "rgba(255, 255, 255, 0.10)");
const text = snapshot.resolveString("text", "white");
const muted = snapshot.resolveString("muted", "rgba(255, 255, 255, 0.78)");
const primary = snapshot.resolveString("accent", "#1A73E8");
const padding = snapshot.insets
? snapshot.insets.contentPadding
: snapshot.scale.insetXY(1.25, 1.25);
const shell = el("div", {
class : "app",
style : {
background : bg,
color : text
}
}, [
el("div", {
class : "shell",
style : {
maxWidth : snapshot.container.maxWidth,
padding : padding
}
}, [
el("div", {
class : "row",
style : { marginBottom : snapshot.scale.space(8) }
}, [
el("div", null, [
el("div", {
style : {
fontSize : ui.font("2xl"),
fontWeight : "700",
letterSpacing : snapshot.scale.tracking("tight"),
lineHeight : snapshot.scale.line("tight")
}
}, ["Axis Demo"]),
el("div", {
style : {
marginTop : snapshot.scale.space(2),
fontSize : ui.font("md"),
lineHeight : snapshot.scale.line("normal"),
color : muted
}
}, ["Universal interface intelligence without breakpoint soup."])
]),
el("div", null, [
el("span", {
class : "pill mono",
style : {
border : "1px solid " + border,
background : "rgba(0, 0, 0, 0.25)",
padding : snapshot.scale.insetXY(0.75, 0.50),
fontSize : ui.font("sm")
}
}, [
"profile : " + snapshot.profile.id
])
])
]),
el("div", {
class : "grid",
style : {
gridTemplateColumns : snapshot.layoutMode === "stack" ? "1fr" : "1fr 1fr",
gap : snapshot.scale.space(8)
}
}, [
el("div", {
class : "card",
style : {
border : "1px solid " + border,
background : cardBg,
borderRadius : snapshot.scale.radius("2xl"),
padding : snapshot.container.cardPadding,
boxShadow : snapshot.scale.shadow("lg")
}
}, [
el("div", {
style : {
fontSize : ui.font("xl"),
fontWeight : "700",
lineHeight : snapshot.scale.line("snug")
}
}, ["Signals"]),
el("div", {
class : "mono",
style : {
marginTop : snapshot.scale.space(4),
fontSize : ui.font("sm"),
lineHeight : snapshot.scale.line("relaxed"),
color : muted
}
}, [
"caps.touch : " + String(snapshot.caps.touch) + "\n" +
"caps.hover : " + String(snapshot.caps.hover) + "\n" +
"orientation : " + snapshot.orientation + "\n" +
"input : " + snapshot.input + "\n" +
"tier : " + snapshot.tier + "\n" +
"shape : " + snapshot.aspect.shape + "\n" +
"aspect : " + snapshot.aspect.ratio.toFixed(3) + "\n" +
"scale.factor : " + snapshot.scale.factor.toFixed(3) + "\n" +
"ui.mode : " + ui.mode + "\n" +
"ui.factor : " + ui.factor.toFixed(3)
])
]),
el("div", {
class : "card",
style : {
border : "1px solid " + border,
background : cardBg,
borderRadius : snapshot.scale.radius("2xl"),
padding : snapshot.container.cardPadding,
boxShadow : snapshot.scale.shadow("lg")
}
}, [
el("div", {
style : {
fontSize : ui.font("xl"),
fontWeight : "700",
lineHeight : snapshot.scale.line("snug")
}
}, ["One UI, Many Screens"]),
el("div", {
style : {
marginTop : snapshot.scale.space(4),
fontSize : ui.font("md"),
lineHeight : snapshot.scale.line("relaxed"),
maxWidth : snapshot.type.measure.maxLineWidth,
color : muted
}
}, [
"AxisUI determines spacing, typography, and control sizing continuously — without breakpoints or platform branches."
]),
el("div", {
style : {
display : "flex",
gap : snapshot.scale.space(4),
marginTop : snapshot.scale.space(7),
flexWrap : "wrap"
}
}, [
el("button", {
class : "btn",
onClick : function()
{
alert(
"AxisUI hit target : " +
ui.hit() + "px (" + ui.hitRem() + ")"
);
},
style : {
background : primary,
color : "white",
borderRadius : snapshot.scale.radius("xl"),
padding : snapshot.scale.insetXY(1.00, 0.75),
fontSize : ui.font("md"),
minHeight : ui.hit() + "px",
boxShadow : snapshot.scale.shadow("md")
}
}, ["Action"]),
el("div", {
class : "pill",
style : {
border : "1px solid " + border,
background : "rgba(0, 0, 0, 0.20)",
borderRadius : snapshot.scale.radius("pill"),
padding : snapshot.scale.insetXY(0.75, 0.55),
fontSize : ui.font("sm"),
color : muted
}
}, [
"ui.mode : " + ui.mode
])
])
])
])
])
]);
root.innerHTML = "";
root.appendChild(shell);
}
axis.subscribe(render);
</script>
</body>
</html>Quick Start — Vue (Vue 3, Composition API)
<template>
<div
:style="{
minHeight : axis.viewport.h(100, 'dynamic'),
background : axis.caps.touch ? 'rgb(10,20,40)' : 'rgb(12,12,14)',
color : 'white',
display : 'flex',
justifyContent : 'center'
}"
>
<div
:style="{
width : '100%',
maxWidth : axis.container.maxWidth,
padding : axis.insets.contentPadding,
boxSizing : 'border-box'
}"
>
<header
:style="{
display : 'flex',
justifyContent : 'space-between',
alignItems : 'center',
marginBottom : axis.ui.space(8)
}"
>
<div>
<div
:style="{
fontSize : axis.type.size('2xl'),
fontWeight : 700
}"
>
Axis Quick Start
</div>
<div style="opacity:0.75">
profile : {{ axis.profile.id }}
</div>
</div>
<button
:style="{
minHeight : axis.ui.hit() + 'px',
padding : axis.scale.insetXY(1.0,0.75),
borderRadius : axis.ui.radius('xl'),
background : '#1A73E8',
color : 'white',
border : 'none',
cursor : 'pointer'
}"
>
Action
</button>
</header>
<main
:style="{
display : 'grid',
gridTemplateColumns :
axis.layoutMode === 'stack'
? '1fr'
: `repeat(${columns},1fr)`,
gap : axis.grid.gutter
}"
>
<div
v-for="n in columns"
:key="n"
:style="{
padding : axis.container.cardPadding,
borderRadius : axis.scale.radius('2xl'),
background : 'rgba(255,255,255,0.06)'
}"
>
Card {{ n }}
</div>
</main>
</div>
</div>
</template>
<script setup>
import { computed, onBeforeUnmount, ref } from "vue";
import createAxisEngine from "@trap_stevo/axis";
const engine = createAxisEngine();
const axis = ref(engine.snapshot());
const unsubscribe = engine.subscribe(next => {
axis.value = next;
});
onBeforeUnmount(() => {
unsubscribe();
engine.destroy();
});
const columns = computed(() => {
return axis.value.grid.pickColumns({
compact : 4,
comfortable : 8,
expanded : 12,
cinema : 12,
default : 8
});
});
</script>📜 License
SCL-1.0-Universal
🧭 One Engine. Every Interface.
Axis provides a single source of truth for interface reality.
From phones to desktops to ultra-wide canvases, layout decisions remain explicit, deterministic, and reusable.
Every device. Every shape.
One layout mind.
