Skip to content

Components

The Nuxt Users module provides several Vue components to help you quickly implement authentication and user management features in your application. These components are designed to work out of the box with minimal configuration while offering extensive customization options.

Available Components

ComponentPurpose
NUsersLoginFormComplete login form with validation and forgot password functionality
NUsersLogoutLinkSimple logout link with confirmation
NUsersResetPasswordFormPassword reset form for users with reset tokens
NUsersListPaginated list of users with management actions
NUsersUserCardIndividual user display card with edit/delete actions
NUsersUserFormForm for creating and editing user accounts
NUsersPasswordStrengthIndicatorDisplays real-time password strength feedback (uses usePasswordValidation composable)

Authentication Components

NUsersLoginForm

A complete login form with built-in validation, error handling, and forgot password functionality.

Basic Usage

vue
<script setup>
const handleLoginSuccess = (user) => {
  console.log('Login successful:', user)
  // Redirect user or update UI
}

const handleLoginError = (error) => {
  console.log('Login failed:', error)
  // Show error message to user
}
</script>

<template>
  <NUsersLoginForm 
    @success="handleLoginSuccess"
    @error="handleLoginError"
  />
</template>

Customization Options

Props

PropTypeDefaultDescription
redirectTostring'/'Where to redirect after successful login

Events

EventPayloadDescription
successUserEmitted when login is successful
errorstringEmitted when login fails

Customization Slots

The login form provides several slots for customization:

vue
<NUsersLoginForm>
  <!-- Custom header -->
  <template #header>
    <div class="custom-header">
      <h2>Welcome Back!</h2>
      <p>Sign in to your account</p>
    </div>
  </template>
  
  <!-- Custom submit button -->
  <template #submit-button>
    <FormKit type="submit" class="custom-button">
      Sign In
    </FormKit>
  </template>
  
  <!-- Custom footer with additional links -->
  <template #footer>
    <div class="login-footer">
      <p>Don't have an account? <a href="/signup">Sign up</a></p>
    </div>
  </template>
</NUsersLoginForm>

A simple logout component that handles user logout with optional confirmation.

Basic Usage

vue
<script setup>
const handleLogoutSuccess = () => {
  console.log('Logout successful')
  // Handle post-logout actions
}

const handleLogoutError = (error) => {
  console.log('Logout error:', error)
  // Handle logout errors
}
</script>

<template>
  <NUsersLogoutLink 
    @success="handleLogoutSuccess"
    @error="handleLogoutError"
  />
</template>

Customization Options

Props

PropTypeDefaultDescription
linkTextstring'Logout'Text displayed in the logout link
redirectTostring'/login'Where to redirect after logout
confirmMessagestring'Are you sure you want to logout?'Confirmation message
classstringundefinedAdditional CSS classes

Examples

vue
<!-- Custom styling and text -->
<NUsersLogoutLink 
  link-text="Sign Out"
  class="custom-logout-btn"
  redirect-to="/home"
/>

<!-- No confirmation dialog -->
<NUsersLogoutLink 
  :confirm-message="null"
  link-text="Quick Logout"
/>

NUsersResetPasswordForm

A form component for users to set a new password using a reset token from their email.

Basic Usage

vue
<template>
  <NUsersResetPasswordForm />
</template>

This component automatically:

  • Reads the token and email from URL query parameters
  • Validates password confirmation
  • Handles API calls for password reset
  • Redirects to login page on success
  • Displays error messages

User Management Components

NUsersList

A comprehensive component for displaying and managing users with pagination, search, and customizable display options.

Basic Usage

vue
<template>
  <div>
    <NUsersList />
  </div>
</template>

Advanced Customization

vue
<script setup>
import { ref } from 'vue'

const { updateUser } = useUsers()

const selectedUser = ref(null)
const handleEditClick = (user) => {
  selectedUser.value = user
}

const handleUserUpdated = async (userData) => {
  selectedUser.value = null
  // Update the user in the local state using the composable - optimistic update
  if (userData.id) {
    updateUser(userData)
  }
  // call the API to update the user
  await $fetch(`/api/nuxt-users/${userData.id}`, {
    method: 'patch',
    body: userData,
  })
}

const handleEdit = (user) => {
  selectedUser.value = user
}

const handleDelete = (user) => {
  // API call is done by the module
  console.log('User deleted:', user)
}
</script>

<template>
    <NUsersUserForm
      v-if="selectedUser"
      :user="selectedUser"
      @submit="handleUserUpdated"
    />

  <NUsersList
    :display-fields="['name', 'email', 'role']"
    :field-labels="{ name: 'Full Name', email: 'Email Address', role: 'Access Level' }"
    @edit-click="handleEdit"
    @delete="handleDelete"
  >
    <!-- Custom title -->
    <template #title>
      <h1>Team Members</h1>
    </template>
    
    <!-- Custom user display -->
    <template #user="{ user, index }">
      <div class="custom-user-item">
        <div class="user-info">
          <h3>{{ user.name }}</h3>
          <p>{{ user.email }}</p>
          <span class="role-badge">{{ user.role }}</span>
        </div>
        <div class="user-actions">
          <button @click="handleEdit(user)">Edit</button>
        </div>
      </div>
    </template>
    
    <!-- Custom pagination -->
    <template #pagination="{ pagination, fetchUsers, loading }">
      <div class="custom-pagination">
        <button 
          :disabled="loading || !pagination.hasPrev" 
          @click="fetchUsers(pagination.page - 1)"
        >
          Previous
        </button>
        <span>Page {{ pagination.page }} of {{ pagination.totalPages }}</span>
        <button 
          :disabled="loading || !pagination.hasNext" 
          @click="fetchUsers(pagination.page + 1)"
        >
          Next
        </button>
      </div>
    </template>
  </NUsersList>
</template>

Props and Events

Props

PropTypeDefaultDescription
displayFieldsstring[]['id', 'name', 'email', 'role', 'created_at']Fields to display for each user
fieldLabelsRecord<string, string>Default labelsCustom labels for fields

Events

EventPayloadDescription
editClickUserFired when edit button is clicked
deleteUserFired after successful user deletion

Available Slots

SlotPropsDescription
title-Custom list title
loading{ loading: boolean }Custom loading indicator
error{ error: string }Custom error display
noUsers-Content when no users found
user{ user: User, index: number }Custom user item display
pagination{ pagination, fetchUsers, loading }Custom pagination controls

Events

EventPayloadDescription
editClickUserFired when edit button is clicked
deleteUserFired after successful user deletion

NUsersUserCard

Displays individual user information with edit and delete actions. Used internally by NUsersList but can be used standalone. The edit and delete events are emitted when the edit or delete button is clicked. The delete event is calling the API to delete the user, while the edit event is just emitting the user object and let the parent component handle the edit action.

Automatic UI Updates

The NUsersList component follows an event-driven pattern for handling user updates. When a user is edited, the component emits events that the parent can handle to update the local state.

Event Flow:

  1. User clicks edit → NUsersUserCard emits editClick
  2. NUsersList forwards the event to parent → Parent receives editClick
  3. Parent shows edit form → User submits changes
  4. Parent updates local state → UI automatically reflects changes

Example with event-driven updates:

vue
<script setup>
import { ref } from 'vue'
import { useUsers } from 'nuxt-users/composables'

const { users, updateUser } = useUsers()
const editingUser = ref(null)

const handleEdit = (user) => {
  editingUser.value = user
}

const handleUserUpdated = (userData) => {
  editingUser.value = null
  // Update the local state using the composable
  if (userData.id) {
    updateUser(userData)
  }
}
</script>

<template>
  <div>
    <NUsersUserForm 
      v-if="editingUser" 
      :user="editingUser" 
      @submit="handleUserUpdated" 
    />
    <NUsersList 
      @edit-click="handleEdit" 
    />
  </div>
</template>

Benefits of this approach:

  • Loose coupling - Components don't need to know about each other's internal methods
  • Event-driven - Clean separation of concerns
  • Reusable - Parent can handle events however it wants
  • Testable - Easy to test event handling
  • Flexible - Parent can implement any update strategy

Basic Usage

vue
<script setup>
const user = { 
  id: 1, 
  name: 'Jane Doe', 
  email: '[email protected]', 
  role: 'user' 
}

const handleEdit = (user) => {
  console.log('Edit user:', user)
}

const handleDelete = (user) => {
  console.log('User deleted:', user)
}
</script>

<template>
  <NUsersUserCard 
    :user="user" 
    :index="0"
    @edit-click="handleEdit"
    @delete="handleDelete"
  />
</template>

Customization

vue
<template>
  <NUsersUserCard
    :user="user"
    :index="0"
    :display-fields="['name', 'role']"
    :field-labels="{ name: 'User Name', role: 'Access Level' }"
  >
    <!-- Completely custom card layout -->
    <template #userCard="{ user }">
      <div class="custom-card">
        <div class="user-avatar">
          <img :src="user.avatar" :alt="user.name" />
        </div>
        <div class="user-details">
          <h4>{{ user.name }}</h4>
          <p>{{ user.email }}</p>
          <span class="role">{{ user.role }}</span>
        </div>
        <div class="user-actions">
          <button @click="handleEdit(user)">Edit</button>
          <button @click="handleDelete(user)">Delete</button>
        </div>
      </div>
    </template>
  </NUsersUserCard>
</template>

NUsersUserForm

A complete form for creating new users or editing existing ones with validation.

Creating New Users

vue
<script setup>
const handleUserCreated = (userData) => {
  console.log('New user created:', userData)
  // Refresh user list or redirect
}
</script>

<template>
  <NUsersUserForm @submit="handleUserCreated" />
</template>

Editing Existing Users

vue
<script setup>
import { ref } from 'vue'

const userToEdit = ref({
  id: 1,
  name: 'Jane Doe',
  email: '[email protected]',
  role: 'user'
})

const handleUserUpdated = (userData) => {
  console.log('User updated:', userData)
  userToEdit.value = null // Close form
}
</script>

<template>
  <NUsersUserForm 
    :user="userToEdit" 
    @submit="handleUserUpdated" 
  />
</template>

Props and Events

Props

PropTypeDefaultDescription
userUser | nullnullUser to edit (null for create mode)

Events

EventPayloadDescription
submitPartial<User>Fired after successful create/update
cancel-Fired when cancel action is triggered

Styling and Theming

All components come with a clean, modern design that works without any CSS framework. The components use a consistent design system with CSS custom properties for easy theming.

Basic Styling

All component styles use the n-users- prefix to avoid conflicts with your application styles:

css
/* Example: Customizing the user list grid */
.n-users-grid {
  gap: 2rem; /* Increase spacing between user cards */
}

/* Example: Styling the delete button */
.n-users-delete-btn {
  background-color: #dc2626;
  border-radius: 6px;
}

Theme Support

The components automatically adapt to light and dark themes based on system preferences. You can also force a specific theme:

Force Light Theme

vue
<template>
  <div class="light">
    <NUsersLoginForm />
  </div>
</template>

Custom Theme Colors

vue
<template>
  <div class="custom-theme">
    <NUsersLoginForm />
  </div>
</template>

<style>
.custom-theme {
  --nu-color-primary: #059669;
  --nu-color-primary-dark: #047857;
  --nu-color-bg-primary: #f0fdf4;
  --nu-color-border: #059669;
}
</style>

Available CSS Custom Properties

PropertyDescriptionLight DefaultDark Default
--nu-color-primaryPrimary brand color#3b82f6#60a5fa
--nu-color-primary-darkDarker primary variant#2563eb#3b82f6
--nu-color-bg-primaryMain background#ffffff#111827
--nu-color-bg-secondarySecondary background#f9fafb#1f2937
--nu-color-borderBorder color#e5e7eb#374151

Complete Styling Example

vue
<template>
  <div class="branded-auth">
    <NUsersLoginForm 
      class="branded-login"
      @success="handleSuccess"
    >
      <template #header>
        <div class="brand-header">
          <img src="/logo.png" alt="Company Logo" />
          <h2>Welcome to MyApp</h2>
          <p>Sign in to access your dashboard</p>
        </div>
      </template>
      
      <template #submit-button>
        <FormKit
          type="submit"
          class="brand-button"
        >
          Sign In to MyApp
        </FormKit>
      </template>
    </NUsersLoginForm>
  </div>
</template>

<style scoped>
.branded-auth {
  --nu-color-primary: #7c3aed;
  --nu-color-primary-dark: #6d28d9;
  --nu-color-bg-primary: #faf5ff;
  --nu-color-border: #c4b5fd;
}

.brand-header {
  text-align: center;
  margin-bottom: 2rem;
}

.brand-header img {
  width: 80px;
  height: 80px;
  margin-bottom: 1rem;
}

.brand-header h2 {
  color: #7c3aed;
  font-size: 1.875rem;
  font-weight: 700;
  margin-bottom: 0.5rem;
}

.brand-button {
  background: linear-gradient(135deg, #7c3aed 0%, #a855f7 100%) !important;
  border: none !important;
  font-weight: 600 !important;
  text-transform: uppercase !important;
  letter-spacing: 0.05em !important;
}
</style>

Troubleshooting

Build-Time Errors

If you encounter build-time errors like this:

[Vue Router warn]: uncaught error during route navigation:
ERROR [nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function.

This is a known issue that has been fixed in the module. The error occurs when components use composables that require Nuxt to be available during the build process.

Solution: Update to the latest version of the module. If you're still experiencing issues, please report it as a bug.

Note: This issue only affects certain components (NUsersList, NUsersUserCard, NUsersLogoutLink, NUsersProfileInfo) and has been resolved by implementing lazy initialization of composables.

Component Not Rendering

If a component is not rendering or appears empty:

  1. Check the console for any JavaScript errors
  2. Verify the component is properly imported and registered
  3. Ensure required props are provided (check the component documentation)
  4. Check that the API endpoints are accessible and returning data

Authentication Issues

If authentication components are not working:

  1. Verify your configuration - Check that apiBasePath is correctly set
  2. Check network requests - Ensure API calls are reaching your server
  3. Review server logs - Look for any server-side errors
  4. Test with the playground - Try the components in the module's playground

Next Steps

Released under the MIT License.