NuGrid provides a built-in add-new-row feature that displays a special row for adding new items to your data.
Enable the add row feature with the add-new-row prop:
<template>
<!-- Simple enable -->
<NuGrid :data="data" :columns="columns" :add-new-row="true" />
<!-- With options -->
<NuGrid
:data="data"
:columns="columns"
:add-new-row="{
position: 'bottom',
addNewText: 'Add New Item',
}"
/>
</template>
| Option | Type | Default | Description |
|---|---|---|---|
position | 'top' | 'bottom' | 'none' | 'bottom' | Position of add row |
addNewText | string | 'Add new...' | Placeholder text |
<template>
<!-- Add row at bottom (default) -->
<NuGrid :add-new-row="{ position: 'bottom' }" />
<!-- Add row at top -->
<NuGrid :add-new-row="{ position: 'top' }" />
<!-- Hidden but accessible via keyboard/API -->
<NuGrid :add-new-row="{ position: 'none' }" />
</template>
Listen to the row-add-requested event when a new row is submitted:
<script setup lang="ts">
const data = ref([...])
function handleRowAddRequested(newRow) {
// Generate ID for the new row
const maxId = Math.max(...data.value.map(d => d.id), 0)
newRow.id = maxId + 1
// Add to data
data.value = [...data.value, { ...newRow }]
// Optionally persist to server
await api.createItem(newRow)
toast.add({
title: 'Row Added',
description: `Added "${newRow.name}"`,
color: 'success',
})
}
</script>
<template>
<NuGrid
:data="data"
:columns="columns"
:add-new-row="true"
@row-add-requested="handleRowAddRequested"
/>
</template>
Configure how columns behave in the add-new-row:
const columns: NuGridColumn<Product>[] = [
{
accessorKey: 'id',
header: 'ID',
showNew: false, // Hide in add row (auto-generated)
},
{
accessorKey: 'name',
header: 'Name',
requiredNew: true, // Required field in add row
},
{
accessorKey: 'category',
header: 'Category',
defaultValue: 'General', // Default value in add row
},
{
accessorKey: 'price',
header: 'Price',
validateNew: (value) => {
if (value !== undefined && value < 0) {
return { valid: false, message: 'Price must be positive' }
}
return { valid: true }
},
},
{
accessorKey: 'quantity',
header: 'Quantity',
showNew: false, // Server-assigned field
},
]
| Option | Type | Description |
|---|---|---|
showNew | boolean | Show/hide column in add row |
requiredNew | boolean | Make field required in add row |
defaultValue | any | Default value for new rows |
validateNew | function | Validation function for add row |
Track the add row state:
<script setup lang="ts">
const gridRef = useTemplateRef('grid')
const addRowState = computed(() => {
return gridRef.value?.addRowState ?? 'idle'
})
</script>
<template>
<div>
<span>Add Row State: {{ addRowState }}</span>
<NuGrid ref="grid" :data="data" :columns="columns" :add-new-row="true" />
</div>
</template>
| State | Description |
|---|---|
idle | Add row not active |
focused | Add row is focused |
editing | Actively editing add row |
| Key | Action |
|---|---|
Arrow Down from last row | Focus add row |
Arrow Up from add row | Return to data rows |
Enter in add row | Submit new row |
Escape in add row | Cancel and clear |
Tab | Move between add row cells |
When grouping is enabled, add rows can appear at the group level:
<script setup lang="ts">
const grouping = ref(['category'])
</script>
<template>
<NuGrid
v-model:grouping="grouping"
:data="data"
:columns="columns"
:add-new-row="{ position: 'bottom' }"
/>
</template>
Combine with validation for robust data entry:
<script setup lang="ts">
import { z } from 'zod'
const productSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
price: z.coerce.number().min(0.01, 'Price must be greater than 0'),
category: z.string().min(1, 'Category is required'),
})
const validationOptions = {
schema: productSchema,
validateOn: 'reward',
showErrors: 'always',
onInvalid: 'block',
}
</script>
<template>
<NuGrid
:data="data"
:columns="columns"
:add-new-row="true"
:validation="validationOptions"
@row-add-requested="handleRowAddRequested"
/>
</template>
The add row receives a data-add-row="true" attribute for styling:
<template>
<NuGrid
:data="data"
:columns="columns"
:add-new-row="true"
:ui="{
tbody: '[data-add-row=true]:bg-primary/5',
}"
/>
</template>
Or with custom CSS:
[data-add-row='true'] {
border-inline: 1px dashed var(--ui-border);
background: linear-gradient(90deg, var(--ui-primary) / 0.06, transparent 35%);
}
[data-add-row='true'] td:first-child::before {
content: '+ Add';
color: var(--ui-primary);
font-weight: 600;
}
<script setup lang="ts">
import { z } from 'zod'
interface Product {
id: number
name: string
category: string
price: number
stock: number
}
const data = ref<Product[]>([
{ id: 1, name: 'Laptop', category: 'Electronics', price: 999, stock: 10 },
{ id: 2, name: 'Mouse', category: 'Electronics', price: 29, stock: 50 },
])
const columns: NuGridColumn<Product>[] = [
{ accessorKey: 'id', header: 'ID', showNew: false },
{ accessorKey: 'name', header: 'Name', requiredNew: true },
{ accessorKey: 'category', header: 'Category', defaultValue: 'General' },
{ accessorKey: 'price', header: 'Price' },
{ accessorKey: 'stock', header: 'Stock', showNew: false },
]
const toast = useToast()
function handleRowAddRequested(newRow: Partial<Product>) {
const maxId = Math.max(...data.value.map(d => d.id), 0)
const product: Product = {
id: maxId + 1,
name: newRow.name!,
category: newRow.category || 'General',
price: newRow.price || 0,
stock: 0,
}
data.value = [...data.value, product]
toast.add({
title: 'Product Added',
description: `Added "${product.name}"`,
color: 'success',
})
}
</script>
<template>
<NuGrid
:data="data"
:columns="columns"
:add-new-row="{ position: 'bottom', addNewText: 'Add New Product' }"
:editing="{ enabled: true, startClicks: 'single' }"
@row-add-requested="handleRowAddRequested"
/>
</template>