Skip to content

Components Guide

Defining a Component

ts
import { defineComponent, h } from 'nexa-framework'

const MyComponent = defineComponent({
  setup(props, { emit, slots }) {
    const count = signal(0)
    return { count }
  },
  render(ctx) {
    return h('div', null, [String(ctx.count.value)])
  },
})

Props and Emit

ts
defineComponent({
  setup(props, { emit }) {
    return {
      submit: () => emit('submit', props.value)
    }
  },
  render(ctx) {
    return h('button', { onClick: ctx.submit }, ['Submit'])
  },
})

Slots

html
<!-- Child component -->
<template>
  <div class="card">
    <header>
      <slot name="header" />
    </header>
    <main>
      <slot />
    </main>
  </div>
</template>
html
<!-- Parent component -->
<template>
  <Card>
    <template #header>
      <h1>Title</h1>
    </template>
    <p>Default slot content</p>
  </Card>
</template>

Scoped Slots

html
<!-- Child passes data to slot -->
<template>
  <ul>
    <li v-for="item in items">
      <slot :item="item" :index="index" />
    </li>
  </ul>
</template>

Lifecycle Hooks

ts
import { onMounted, onBeforeUnmount, onUpdated } from 'nexa-framework'

setup() {
  onMounted(() => console.log('mounted'))
  onBeforeUnmount(() => console.log('cleanup'))
  onUpdated(() => console.log('re-rendered'))
}
  • v-show — toggles display via signal
  • v-model — two-way binding for inputs
  • v-if — conditional rendering
  • v-for — list rendering

Event Modifiers

Nexa supports event modifiers to handle common event patterns efficiently:

  • .prevent — calls event.preventDefault()
  • .stop — calls event.stopPropagation()
  • .self — only triggers if the event was dispatched from the element itself (not a child)
html
<button @click.prevent="submit">Submit</button>
<div @click.self="closeModal">Click outside to close</div>

Component Resolution

Nexa components should be named using PascalCase in templates to distinguish them from standard HTML elements. The compiler automatically resolves these names from the component's setup context.

html
<template>
  <MyCustomButton>Click Me</MyCustomButton>
</template>

<script setup>
import MyCustomButton from './MyCustomButton.nexa'
</script>

Transitions

Nexa provides enter/leave transitions via _enter and _leave props on any VNode:

ts
import { h, mount } from 'nexa-framework'

const vnode = h('div', {
  _enter: 'fade',    // apply 'fade-enter-*' classes on mount
  _leave: 'fade',    // apply 'fade-leave-*' classes on unmount
}, ['Hello'])

Enter Animation

When a VNode with _enter is mounted:

  1. {name}-enter-from + {name}-enter-active classes are applied immediately
  2. On the next animation frame, -enter-from is removed and -enter-to is added
  3. On transitionend/animationend (or 300ms timeout), all enter classes are removed

Leave Animation

When a VNode with _leave is removed:

  1. {name}-leave-from + {name}-leave-active classes are applied
  2. DOM removal is deferred until the animation completes
  3. On the next frame, -leave-from is removed and -leave-to is added
  4. On transitionend/animationend (or 300ms timeout), the element is removed

CSS Example

css
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.3s ease;
}
.fade-enter-from, .fade-leave-to {
  opacity: 0;
}

Default Prefix

Pass true instead of a name to use the v- prefix:

ts
h('div', { _enter: true }, ['content'])
// uses v-enter-from, v-enter-active, etc.

Released under the MIT License.