Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Forms

Invoice line items

Forms

Invoice line items React component for Tailwind CSS v4

Inline EditRich Text Editor
Live props
Behavior
allowCustomProducts
pricesIncludeVat
defaultVatRate
defaultDiscountType
reorderable
minLines1
maxLinesNo limit
name
undoTimeout5000ms
Content
currency
locale
Layout
showDescription
showUnit
showVat
showDiscount
showTotals
totalsPosition
layout
State
readOnly
disabled
Appearance
size
variant
Code
import { InvoiceLineItems } from "@/components/ui/invoice-line-items"

<InvoiceLineItems />
  1. Home
  2. Components
  3. Forms
  4. Invoice line items
Pro

Invoice line items React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React invoice line items editor that works like a small spreadsheet, with VAT per rate computed the e-Factura way.

Type in a product cell to search the catalog by name, SKU or keyword; picking one fills its unit, price and VAT, and typing a new name adds it as free text with the price flagged until you enter one. Tab moves across the cells in reading order, Escape in a number cell puts back the value it had, and an empty cell shows its prompt only on the line you are in. Enter moves to the same cell of the next line; on the last line it walks right through the text cells and adds a line from the last one, and Cmd or Ctrl + Enter adds a line from anywhere in the table. Drag a line by its grip with the mouse, or focus the grip and press Space to lift, the arrows to move, Space to drop and Escape to cancel; Alt + Arrow Up / Down moves the line you are in, and Delete or Backspace on a focused grip removes it. The ⋯ menu duplicates, moves (⌥↑ / ⌥↓ work from any cell) and removes; a removed line leaves an undo row for five seconds, paused while you hover or focus it. The % / lei chip in a discount cell switches the discount type. Totals update on every keystroke: subtotal, discounts, the taxable base, VAT per rate and the grand total, which is announced once your edits settle. Narrower than 768px (or with layout cards) every line is a card: tap it to edit a copy in a bottom sheet (a dialog on desktop) and keep it with Save or Cmd / Ctrl + Enter, while closing the sheet discards the draft; long-press the grip to reorder, use ⋯ for Move up / Move down, and tap the Total row to open the breakdown.

Invoice line items is a Pro React component in the React form components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add invoice-line-items, together with Badge, Button, Combobox, Currency Input, Drawer and 14 more and the npm packages @dnd-kit/core, @dnd-kit/sortable, motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add invoice-line-items

Invoice line items is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import InvoiceLineItems from @/components/ui/invoice-line-items.

import { useState } from "react"
import {
  InvoiceLineItems,
  InvoiceTotals,
  type InvoiceDocumentDiscount,
  type InvoiceLine,
  type InvoiceProduct,
} from "@/components/ui/invoice-line-items"

const PRODUCTS: InvoiceProduct[] = [
  { id: "crm-business", name: "Northwind CRM Business license", unit: "month", unitPrice: 250, vatRate: 0.21, sku: "CRM-BUS" },
  { id: "onboarding", name: "Setup and training", unit: "hour", unitPrice: 320, vatRate: 0.21 },
  { id: "guide", name: "Printed user guide", unit: "pc", unitPrice: 64.9, vatRate: 0.11 },
]

export function InvoiceEditor() {
  const [lines, setLines] = useState<InvoiceLine[]>([
    { id: "l1", productId: "crm-business", product: "Northwind CRM Business license", quantity: 25, unit: "month", unitPrice: 250, vatRate: 0.21, discount: 10 },
  ])
  const [discount, setDiscount] = useState<InvoiceDocumentDiscount | null>(null)
  return (
    <InvoiceLineItems
      value={lines}
      onValueChange={(next, totals) => {
        setLines(next)
        console.log("Amount due", totals.total)
      }}
      products={PRODUCTS}
      units={["pc", "hour", "day", "month", "service"]}
      documentDiscount={discount}
      onDocumentDiscountChange={setDiscount}
      name="invoice[lines]"
      currency="RON"
      locale="en-US"
      labels={{ grandTotal: "Amount due" }}
    />
  )
}

// the totals block alone (an invoice preview): pass lines or precomputed totals.
// DEFAULT_INVOICE_LINE_ITEMS_LABELS holds the English strings to spread under a translation
export function InvoicePreviewTotals({ lines }: { lines: InvoiceLine[] }) {
  return <InvoiceTotals lines={lines} currency="RON" locale="en-US" />
}

Props

  • value
    InvoiceLine[]

    The lines (controlled). InvoiceLine is the lib/invoicing line { id, quantity, unitPrice, vatRate?, discount?, discountType? } plus product, productId, description, unit and vatCategory (tells the 0% rows apart: E exempt, AE reverse charge, K intra-community).

  • defaultValue
    InvoiceLine[]

    The starting lines while uncontrolled. Left out: one empty line (none when minLines is 0).

  • onValueChange
    (lines: InvoiceLine[], totals: InvoiceTotalsData) => void

    Every edit, with the totals from computeInvoiceTotals (net, VAT per rate, total).

  • productsdefault "[]"
    InvoiceProduct[]

    The product combobox's options: { id, name, description?, unit?, unitPrice, vatRate?, sku?, keywords? }. Name, SKU and keywords are searched; picking one fills the unit, price and VAT and keeps the quantity.

  • onSearchProducts
    (query: string) => Promise<InvoiceProduct[]>

    Async catalog search (the combobox's async mode, debounced, older answers dropped).

  • allowCustomProductsdefault true
    boolean

    Free text in the product cell with an “Add …” row. A new product keeps its price empty and flagged until one is entered.

  • currencydefault "RON"
    string

    ISO 4217 code for prices and totals. Changing it never converts the amounts.

  • pricesIncludeVatdefault false
    boolean

    The price column is the price with VAT (B2C quotes, shops); the totals end with “of which VAT”. Math per lib/invoicing either way.

  • vatRatesdefault "RO_VAT_RATES"
    VatRateOption[]

    The VAT select's options: { value, rate, category, label, description }. Default: 21%, 11%, 0% exempt, 0% reverse charge, 0% intra-community. A line with a rate not in the list shows it as text.

  • defaultVatRatedefault "0.21"
    number

    The VAT rate of new lines, as a fraction.

  • unitsdefault "["pcs", "hr", "day", "month", "kg", "m²", "service"]"
    string[]

    The unit list; any other unit can be typed.

  • showDescriptiondefault true
    boolean

    A second line under the product for a description.

  • showUnitdefault true
    boolean

    The unit column.

  • showVatdefault true
    boolean

    The VAT column.

  • showDiscountdefault true
    boolean

    The discount column, with a % / amount switch per line.

  • defaultDiscountTypedefault "percent"
    "percent" | "amount"

    The discount type of new lines.

  • documentDiscountdefault "null"
    { type: "percent" | "amount"; value: number } | null

    A discount on the whole document, shared across the lines by value before VAT.

  • onDocumentDiscountChange
    (discount: InvoiceDocumentDiscount | null) => void

    Makes the totals' discount row editable.

  • showTotalsdefault true
    boolean

    The totals block under the lines.

  • totalsPositiondefault "below"
    "below" | "end"

    below: a full-width block; end: a narrow block at the end under the table (always full width on phones).

  • layoutdefault "auto"
    "auto" | "table" | "cards"

    auto: the spreadsheet table from 768px of the component's own width (not the screen's), cards with a sheet editor below it, so a narrow panel on a desktop gets the cards too. table and cards force one (the table scrolls inside its panel when it does not fit).

  • reorderabledefault true
    boolean

    Drag handles (dnd-kit: mouse, a long press on touch, the keyboard), Alt + arrows and Move up / down in the menu.

  • minLinesdefault 1
    number

    Lines that always stay: removing one of the last ones clears it instead. 0 shows an empty state when there are none.

  • maxLinesdefault 0
    number

    The most lines; Add and Duplicate turn off at the limit. 0 means no limit.

  • errors
    Record<string, Partial<Record<"product" | "quantity" | "unitPrice" | "vatRate" | "discount", string>>>

    Server validation per line id and field: the cell turns invalid with the message in a tooltip (desktop) or under the field (the sheet).

  • readOnlydefault false
    boolean

    The invoice as the customer sees it: a plain table without inputs, the same totals.

  • disableddefault false
    boolean

    Every cell, handle and button is disabled.

  • sizedefault "md"
    "sm" | "md"

    Cell controls 32 / 40px (44px on touch).

  • variantdefault "ghost"
    "ghost" | "soft"

    ghost: a spreadsheet feel with a hover tint; soft: filled fields.

  • namedefault ""
    string

    A hidden input with the lines as JSON, for native forms.

  • localedefault ""
    string

    Numbers and money format with this locale; empty uses the UIProvider's, then the page's.

  • undoTimeoutdefault 5000
    number

    Milliseconds the undo row stays after a line is removed.

  • labelsdefault "DEFAULT_INVOICE_LINE_ITEMS_LABELS"
    Partial<InvoiceLineItemsLabels>

    Every string: the order, index and actions headers, column names (product, description, quantity, unit, unitPrice, unitPriceWithVat, vat, discount, total), actions (addLine, duplicate, moveUp, moveDown, remove, undo), the add row's lineCount(count, formatted), the empty state (empty, emptyDescription), the cells (descriptionPlaceholder, untitled), the pickers (productPlaceholder, createProduct(query), noProducts, unitPlaceholder, createUnit(query), priceMissing, newProduct, discountType, percent, amount), totals (gross, lineDiscounts, documentDiscount, net, vatRow(rate, base), grandTotal, includedVat(rate), showBreakdown, hideBreakdown), the sheet (editLine, saveLine, cancel, lineTitle(n), lineTotal), accessible names (caption, cellLabel(column, n), lineActions(n), reorder(n)), announcements (removed(n, product), moved(from, to), added(n), totalAnnouncement(total)) and the drag messages (pickedUp, movedTo, dropped, dragCancelled).

  • classNames
    Partial<Record<InvoiceLineItemsSlot, string>>

    Classes for panel, table, head, row, handle, index, product, description, quantity, unit, price, vat, discount, total, actions, add, cards, card, sheet, empty, live and totals (the panel around InvoiceTotals).

Parts

  • InvoiceTotals

    component

    The totals block on its own (an invoice preview or a sidebar): subtotal, document discount, VAT per rate and the total, from lines or precomputed totals.

    • lines
      InvoiceLine[]

      The lines to compute the totals from.

    • totals
      InvoiceTotalsData

      Precomputed totals from computeInvoiceTotals; left out, they are computed from lines.

    • pricesIncludeVatdefault false
      boolean

      Unit prices already include VAT.

    • documentDiscountdefault "null"
      { type: "percent" | "amount"; value: number } | null

      A discount on the whole document, shown as its own row.

    • onDocumentDiscountChange
      (discount: InvoiceDocumentDiscount | null) => void

      Makes the discount row editable.

    • currencydefault "RON"
      string

      ISO 4217 code for the amounts.

    • locale
      string

      Intl locale for the amounts; falls back to the UIProvider locale, then the browser.

    • collapsibledefault false
      boolean | "auto"

      One Total row that opens the breakdown; auto does it while the invoice container is narrower than 768px.

    • readOnlydefault false
      boolean

      Shows the totals without any editable row.

    • labels
      Partial<InvoiceLineItemsLabels>

      Overrides for the totals' strings.

  • DEFAULT_INVOICE_LINE_ITEMS_LABELS

    constant

    Every English string of the editor and the totals, to spread under a translation and override a few keys.

  • InvoiceLine

    type

    One line: id, quantity, unitPrice, vatRate, discount and discountType from lib/invoicing, plus product, productId, description, unit and vatCategory.

  • InvoiceProduct

    type

    A catalog entry for the product picker: { id, name, unitPrice, description?, unit?, vatRate?, sku?, keywords? }.

Examples

Quote with prices incl. VAT

pricesIncludeVat for B2C: the price column is “Price incl. VAT” and the VAT shows as an “incl. VAT” row under the total.

Read-only preview

The invoice as the customer sees it: plain text, the same totals, a 5% document discount.

Server errors

errors marks cells per line and field (a tooltip in the table, a line of text on the cards). A retired 9% rate stays visible as text.

Reverse charge in EUR

An EU client: VAT 0% with category AE (reverse charge) from vatRates, amounts in euro.

Compact table

layout table, size sm, soft cells, no description line, totals in a narrow block at the end.

Cards and the sheet editor

layout cards: tap a line to edit a copy (a bottom sheet on phones, a dialog on desktop) and keep it with Save line.

Credit note

Negative quantities for a storno: line values, VAT and the total keep their minus sign.

Async catalog search

onSearchProducts asks a server as you type; allowCustomProducts off keeps the lines to catalog products.

Dependencies

@dnd-kit/core@dnd-kit/sortablemotionlucide-reactbadgebuttoncomboboxcurrency-inputdrawerdropdown-menuempty-stateinputnumber-inputsegmented-controlselecttooltipuse-controllable-stateuse-localecommerceinvoicingmotionnumber-formatui-config

Files

  • components/ui/invoice-line-items.tsx

FAQ

Is the React Invoice line items component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Invoice line items?

Run npx wingo-ui@latest add invoice-line-items. It also adds the Wingo UI items it builds on (Badge, Button, Combobox, Currency Input, Drawer and 14 more) and the npm packages @dnd-kit/core, @dnd-kit/sortable, motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/invoice-line-items.

How does Invoice line items behave on phones?

Narrower than 768px (or with layout cards) every line is a card: tap it to edit a copy in a bottom sheet (a dialog on desktop) and keep it with Save or Cmd / Ctrl + Enter, while closing the sheet discards the draft; long-press the grip to reorder, use ⋯ for Move up / Move down, and tap the Total row to open the breakdown.

See also

InvoicinglibCommercelibCurrency InputComboboxTableApproval Flow

Used in

Invoice Page

More React form components

Rich Text EditorSchema BuilderAddress InputBooking WidgetCSV ImportField
All 9 React form components

Articles using this component

  • React Invoice Template: Line Items, VAT and Print View

    Build it: screens and apps · Oct 16, 2026 · 10 min read

  • E-Factura VAT Calculation in TypeScript and React

    Build it: screens and apps · Oct 9, 2026 · 11 min read

PreviousInline EditNext Rich Text Editor

Tax invoice

Draft

CRP #2026-0412

Tech Solutions SRL · CUI RO12345674 · Cluj-Napoca

IssuedSep 24, 2026
DueOct 24, 2026
Invoice lines
Order#Product or serviceQtyUnitUnit priceVATDiscountTotalActions
1
lei250.00Amount in Romanian lei
lei 5,625.00
2
lei320.00Amount in Romanian lei
lei 1,920.00
3
lei1,500.00Amount in Romanian lei
lei 1,500.00
4
lei64.90Amount in Romanian lei
lei 778.80
4 lines
  • 1.

    25 × lei 250.00VAT 21%-10%

    25 users, October 2026

    lei 5,625.00
  • 2.

    6 × lei 320.00VAT 21%

    On-site sessions at the client's office, Cluj-Napoca

    lei 1,920.00
  • 3.

    1 × lei 1,500.00VAT 21%

    lei 1,500.00
  • 4.

    12 × lei 64.90VAT 11%

    lei 778.80
Subtotal
lei 10,448.80
Line discounts
-lei 625.00
Total excl. VAT
lei 9,823.80
VAT 21% on lei 9,045.00
lei 1,899.45
VAT 11% on lei 778.80
lei 85.67
Total
lei 11,808.92

Narrower than 768px the lines turn into cards with a sheet editor; layout forces the table or the cards.

total lei 11,808.92 · VAT 21% lei 1,899.45 · VAT 11% lei 85.67 · 4 lines · minLines 1 · maxLines ∞ · allowCustomProducts true · defaultDiscountType percent · defaultVatRate 0.21 · undoTimeout 5000ms · layout auto