Building Your First Component
This tutorial builds a small, realistic component with typed props, actions, layout, and conditional rendering. It assumes you’ve completed Getting Started and skimmed the Language Tour.
1) Define types and props
Section titled “1) Define types and props”Start a file named profile-card.nx, or work in the playground:
type User = { id:string name:string title?:string avatarUrl?:string }type ProfileAction = { id:string label:string }
external component <Button label:string emits { Pressed { } } />- Optional fields carry
?on their name and may be left out. Buttonis anexternalcomponent: the host application supplies it, and NX checks every use against its props and thePressedaction it emits.- Keep types beside the component so they stay in sync.
2) Lay out the component
Section titled “2) Lay out the component”Add the component, with sensible defaults. Each block from here on continues the same file:
component <ProfileCard user:User actions?:ProfileAction+ tone:string = "neutral" emits { ActionChosen { actionId:string } }/> = { <article className={"card tone-" + tone}> <header> <img src={user.avatarUrl ?? "/placeholder.png"} alt={user.name}/> <div> <h3>{user.name}</h3> if user.title? { <p>{user.title}</p> } </div> </header> <footer> for item in actions { <Button label={item.label} onPressed=<ProfileCard.ActionChosen actionId={item.id}/> /> } </footer> </article>}- Attributes and body content accept expressions (including
ifandfor) without leaving markup mode. - Defaults keep the call site concise, and an optional prop such as
actionscan be left out entirely: it is then empty, and theforover it yields nothing. emitsdeclares the actions the card produces. An action declared inline gets the public nameProfileCard.ActionChosen, and a button’sonPressedhandler builds one.
3) Handle actions
Section titled “3) Handle actions”A handler is not a callback that runs code; it is an expression that builds an action record. The screen that renders the card turns the card’s action into one for the host:
action ProfileActionRequested = { userId:string actionId:string }
let <ProfileScreen user:User/> = <ProfileCard user={user} tone="info" actions={ <ProfileAction id="message" label="Message"/> <ProfileAction id="follow" label="Follow"/> } onActionChosen=<ProfileActionRequested userId={user.id} actionId={action.actionId}/> />on<ActionName>binds a handler to an action the component emits. Inside it,actionis the emitted record, and the handler can also read props such asuser.- Because the card declares
ActionChosen { actionId:string }, the compiler checks thataction.actionIdexists and that the handler returns an action. - The host receives
ProfileActionRequestedand performs the side effect, such as sending a message.
4) Render it
Section titled “4) Render it”End the file with a root element to see everything together:
let currentUser = <User id="42" name="Kai" title="Designer"/>
<ProfileScreen user={currentUser}/>Because ProfileCard is a component, the result is a ProfileCard record holding its props and
its bound handler. A host initializes that record to render the card and dispatches actions back to
it.
5) Validate and iterate
Section titled “5) Validate and iterate”- Paste the whole file into the playground: it compiles as you type and reports errors against your source.
- Use the patterns from the Reference (especially Functions & Components and if) to refactor as the component grows.
6) Extend the pattern
Section titled “6) Extend the pattern”- Add a
status:Statusfield, withtype Status = online | away | offline, and render a badge usingif user.status is { ... }. - Introduce a
contentproperty, such ascontent extra?:Element, to let callers inject extra markup into the footer. - Give the card local state,
state { following:boolean = false }, and have a Follow button return<Update following={!following}/>instead of an action. - Thread design tokens (see the next tutorial) into
toneand button styling so the component respects theming.