| name | atomico |
| description | Core entry-point router, orchestrator, and reference manual for all Atomico.js tasks. Contains coding standards, API cheat sheets, architectural guidelines, and validation rules.
|
| license | MIT |
| compatibility | Atomico >=2.0, TypeScript >=5.0 |
| metadata | {"category":"core","priority":"highest"} |
Atomico.js — Consolidated Development & Validation Guide
Unified reference for building, auditing, and validating Atomico.js web components.
AGENT CONTRACT: You may not respond to a code generation request
without completing the Validation Report in §7.
If any check fails, fix the code before responding — do not report
failures as acceptable output.
1. Hello World Component
Every Atomico component is defined with c() and must return a <host> root element.
import { c, useProp, css } from "atomico";
export const MyCounter = c(
({ label }) => {
const [value, setValue] = useProp<number>("value");
return (
<host shadowDom>
<button onclick={() => setValue(value + 1)}>
{label}: {value}
</button>
</host>
);
},
{
props: {
label: { type: String, value: () => "Increment" },
size: {
type: String,
reflect: true,
value: (): "normal" | "small" => "normal"
},
value: { type: Number, value: () => 0 }
},
styles: css`
:host {
--font-size: 1em;
}
:host([size="small"]) {
--font-size: 0.5em;
}
`
}
);
2. Architectural Rules
- Modular by Default: Avoid monolithic files. Split complex views into a
components/ folder.
- Reuse First: Audit existing workspace components before generating new ones.
- Prop-Driven Communication: Pass data down via props, dispatch events up.
- Separate Registration: NEVER call
customElements.define inside the component file. Centralize all registrations in an index file.
import { MyCounter } from "./my-counter.js";
customElements.define("my-counter", MyCounter);
3. Props
Type system
type AtomicoPropType =
| StringConstructor
| NumberConstructor
| BooleanConstructor
| ArrayConstructor
| ObjectConstructor
| MapConstructor
| SetConstructor
| PromiseConstructor
| (new (...args: any[]) => HTMLElement);
type PropShorthand = AtomicoPropType;
interface PropConfig<T> {
type: AtomicoPropType;
value?: () => T;
reflect?: boolean;
}
Declaration syntax
Use the simplest form that satisfies the requirement:
props: {
label: String,
count: { type: Number, value: () => 0 },
variant: { type: String, reflect: true, value: (): "primary" | "danger" => "primary" }
}
Forbidden:
props: {
name: { type: String },
count: { type: Number, value: 0 }
}
Arrays & Objects — strict return-type annotation
Without an explicit return type, TypeScript resolves Array to never[] and Object to {}.
Annotate the factory return type to guarantee correct inference in TSX:
interface Option { value: string; label: string; }
interface Config { theme: "light" | "dark"; debug: boolean; }
props: {
options: { type: Array, value: (): Option[] => [] },
config: { type: Object, value: (): Config => ({ theme: "light", debug: false }) }
}
Reflect rules
reflect: true is only for visual / CSS states. Allowed types: String, Number, Boolean.
props: {
disabled: { type: Boolean, reflect: true },
variant: { type: String, reflect: true, value: () => "primary" }
},
styles: css`
:host([disabled]) { opacity: 0.5; pointer-events: none; }
:host([variant="danger"]) { background: red; }
`
Never use reflect: true on Array or Object — it triggers expensive DOM serialization.
Events & Callbacks
declare function event<Detail = void>(options?: {
bubbles?: boolean;
composed?: boolean;
cancelable?: boolean;
}): EventDescriptor<Detail>;
declare function callback<
Fn extends (...args: any[]) => NonNullable<unknown>
>(): Fn | undefined;
export const ActionButton = c(
(props) => (
<host>
<button onclick={() => props.action({ id: 42 })}>Fire</button>
</host>
),
{
props: {
action: event<{ id: number }>({ bubbles: true, composed: true })
}
}
);
Shadow DOM boundary rule: Native events like change and submit have composed: false
and cannot cross the Shadow DOM boundary. Always declare a custom event() with
{ bubbles: true, composed: true } and dispatch it explicitly.
export const UiSelect = c(
({ change, options }) => (
<host shadowDom>
<select onchange={(e) => change(e.currentTarget.value as string)}>
{options.map((o) => (
<option value={o.value}>{o.label}</option>
))}
</select>
</host>
),
{
props: {
options: {
type: Array,
value: (): { value: string; label: string }[] => []
},
change: event<string>({ bubbles: true, composed: true })
}
}
);
export const TextEditor = c(
({ content, save }) => (
<host shadowDom>
<button
onclick={async () => {
if (save) {
const ok = await save(content);
if (ok) console.log("Saved!");
}
}}
>
Save
</button>
</host>
),
{
props: {
content: { type: String, value: () => "" },
save: callback<(content: string) => Promise<boolean>>()
}
}
);
Forbidden:
props: {
onChange: event();
}
props: {
save: callback<() => void>();
}
const dispatchInput = useEvent("input");
4. State Management
declare function useProp<T>(name: string): [T, (val: T) => void];
declare function useState<T>(init: T | (() => T)): [T, (val: T) => void];
declare function useObjectState<T extends object>(
init: T
): [T, (partial: Partial<T>) => void];
Decision tree
Does the state need to be read from outside (parent / CSS)?
+- YES -> declare it in props
| +- Does the child also write to it? -> useProp<T>()
| +- Read-only? -> direct destructuring ({ myProp })
+- NO -> private state
+- Single value (boolean, string)? -> useState
+- Two or more related values? -> useObjectState<T>
Correct patterns
export const Badge = c(
({ label, variant }) => (
<host shadowDom>
<span class={variant}>{label}</span>
</host>
),
{
props: {
label: String,
variant: { type: String, reflect: true, value: () => "info" }
}
}
);
export const Counter = c(
({ label }) => {
const [count, setCount] = useProp<number>("count");
return (
<host>
<button onclick={() => setCount(count + 1)}>
{label}: {count}
</button>
</host>
);
},
{
props: {
label: String,
count: { type: Number, value: () => 0 }
}
}
);
export const SearchBar = c(() => {
const [state, setState] = useObjectState({ query: "", filter: "all" });
return (
<host shadowDom>
<input
value={state.query}
oninput={(e) =>
setState({ query: e.currentTarget.value as string })
}
/>
</host>
);
});
Forbidden:
const [label] = useProp("label");
const [query, setQuery] = useState("");
const [filter, setFilter] = useState("all");
const filtered = useMemo(() => items.filter(isActive), [items]);
const filtered = items.filter(isActive);
const done = items.length - filtered.length;
5. JSX
Handlers — Always Inline
The TSX compiler automatically infers the event target type when handlers are inline.
<input oninput={(e) => setState({ query: e.currentTarget.value as string })} />;
const handleInput = (e: any) => setState({ query: e.currentTarget.value });
<input oninput={handleInput} />;
Rule: Single-use handlers go inline. Extract only if the exact same function is
shared across multiple elements.
useRef — no null initialization