Quick Start

Learn NativeCoreJS

Welcome to the NativeCoreJS docs. This page introduces the core ideas you will use every day — the same spirit as a framework quick start, with real code and live previews.

You will learn

  • How to scaffold a NativeCoreJS app
  • How views and routes work together
  • How ref binds the DOM into controllers (no querySelector)
  • How controllers hold reactive state
  • How to use framework nc-* components
  • How custom components wrap content with slots
  • How this.on connects events with automatic cleanup

Install the framework

Create a project with the official scaffolder. JavaScript is the default; choose TypeScript when prompted (or pass --ts).

npx create-nativecore@latest my-app cd my-app npm run dev

Open http://localhost:8000 when the server starts.

Views and routes

NativeCoreJS apps are made of HTML views registered on the router. Generate a page with a short command — the CLI asks a few questions, then sets things up.

npm run make:view hello

Example prompts:

Should this route require login? (y/n): n Route path (/hello): Create a controller for this view? (y/n): y

At Route path, press Enter for the default, or type a dynamic path such as /hello/:id, /tasks/:id, or /files/*. Params land on the controller as params.id (etc.). The route is still registered automatically either way. What the generator will not do for dynamic paths is add a header/sidebar menu link — there is no single URL to put in a menu (you need a real id, e.g. <a href="/hello/42">). Add those links yourself when you have a value.

Router update is automatic. make:view appends the new route to src/routes/routes.js for you (public or protected group based on your answers). You can still edit or add r.register(...) entries manually anytime.

r.register( '/hello', 'src/views/public/hello.html', lazyController('helloController', '../controllers/hello.controller.js') );

Result

Hello route Visit /hello — view, controller, and route are ready.

Controllers and reactive state

Controllers are the logic layer for a view. Use signals to remember values and update the screen — similar in spirit to React state, without a virtual DOM.

Mark elements in the HTML with ref="…". The framework binds those into the controller as this.<name> — so you never need document.querySelector or document.getElementById.

<p ref="countEl">0</p> <button ref="incBtn" type="button">Increment</button>

ref="countEl" becomes this.countEl; ref="incBtn" becomes this.incBtn.

export class CounterController extends CoreController { onMount() { // signal → [get, set] tuple (handy for local UI) const [count, setCount] = this.signal(0); this.bind(count, this.countEl); this.on(this.incBtn, 'click', () => setCount(n => n + 1)); // Same reactivity with state → { value } object // Prefer when you want a named field on the controller: // this.count = this.state(0); // this.bind(this.count, this.countEl); // this.on(this.incBtn, 'click', () => { this.count.value++; }); } }

Live example

Clicked 0 times

this.signal and this.state share the same reactive core. Use signal for a Solid-style [get, set] tuple; use state when you want this.count.value as a field. Both work with this.bind.

Notice there is no onclick in the HTML. Events are attached in the controller with this.on(...) — see Respond to events for why.

Compose with components

The framework ships a nc-* library. Nest components in HTML views the same way you nest UI pieces in other frameworks — except markup stays markup.

<div class="profile"> <nc-avatar alt="Ada Lovelace" variant="primary"></nc-avatar> <h3>Ada Lovelace</h3> <nc-badge count="3" variant="info"><span>notes</span></nc-badge> </div>

Live example

Ada Lovelace
notes

Wrap content with slots

Custom components (via make:component or by hand) use Shadow DOM <slot> to wrap light-DOM children — a header, a body, buttons, other nc-* tags — without owning their markup.

template() { return html\` <div class="panel"> <header class="panel__header"> <slot name="header">Panel</slot> </header> <div class="panel__body"> <slot></slot> </div> </div> \`; }
<learn-panel> <strong slot="header">Welcome card</strong> <p>Default slot holds any wrapped content.</p> <nc-button variant="primary">Continue</nc-button> </learn-panel>

Live example

Welcome card

Default slot holds any wrapped content.

Continue

Put slot="header" on the child that should land in the named slot; everything else fills the default <slot>. Generate a starter with npm run make:component my-card — the scaffold already includes a default slot.

Respond to events

Prefer connecting events in the controller with this.on(...) instead of putting onclick / onchange attributes on the HTML. Keep markup for structure; keep behavior in the controller.

this.on(this.incBtn, 'click', () => setCount(n => n + 1)); // │ │ └─ handler // │ └─ native event name (or a component event like 'change') // └─ target from ref (this.incBtn)

Why not onclick on the element? this.on registers the listener with the controller’s cleanup registry. When the user navigates away, NativeCoreJS tears down the view and removes those listeners automatically — so you do not leak handlers or hold onto old DOM nodes. Inline onclick and hand-rolled addEventListener without cleanup do not get that.

Pair this.on with a signal and this.bind: the event updates state, and the binding keeps the DOM in sync — no manual textContent assignment. Framework components emit short names like change and open:

onMount() { const [greeting, setGreeting] = this.signal('Hello, friend'); this.bind(greeting, this.greetingEl); this.on(this.nameInput, 'change', (e) => { const name = (e.detail?.value || '').trim() || 'friend'; setGreeting(`Hello, ${name}`); }); }

Live example

Hello, friend

Use any npm module

The scaffold vendors the framework under .nativecore/ (zero framework runtime production deps). Your app is still a normal npm project — install charts, date libs, icon packs, or other Web Component libraries whenever you need them.

npm install dayjs # then import in a controller or component: # import dayjs from 'dayjs';

npm run dev / compile runs sync-importmap, which writes a browser import map (and ESM-shims CommonJS packages when needed). Bare imports like from 'dayjs' resolve without hand-editing index.html. Built-in nc-* tags are optional convenience — not a closed garden.

Next steps

You now know the basics of writing NativeCoreJS apps: scaffold, routes, controllers, components, slots, events, and npm modules.

  • Auth is author-owned — add guards with make:middleware
  • Production: npm run build (or build:full) → publish _deploy/
  • Apps vendor .nativecore/; add any app npm deps you need via import map