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

Tables & Lists

Table

Tables & Lists

Table React component for Tailwind CSS v4

Permissions MatrixTask List
Live props
Content
caption
hideCaption
emptyTitle
Appearance
variant
density
striped
State
loading
skeletonCount5
Layout
showFooter
stickyHeader
stickyFirstColumn
maxHeightNone
mobileLayout
mobileSort
cardVariant
Behavior
locale
currency
Code
import { Table } from "@/components/ui/table"

<Table />
  1. Home
  2. Components
  3. Tables & Lists
  4. Table
Pro

Table React component for Tailwind CSS v4

v1.0.1·Updated in 1.2.0

A React table component: semantic table parts plus a props API for small read-only tables that sort, total and turn into cards on phones.

Click a sortable header to sort: text sorts A to Z first, numbers and dates biggest or newest first, a third click clears it, and the arrow flips on a spring. Clickable rows tint on hover and press; Tab reaches each row's first cell, where Enter or Space opens it, and the row shows a focus ring; Shift+F10 or the context menu key opens that row's actions menu. Row actions fade in on hover or focus and stay visible on touch. A table wider than its box scrolls sideways with a fade at the edge that still has content, and a pinned first column casts a hairline shadow once rows slide under it. On a phone every row becomes a card (title, amount at the top end, up to four label and value pairs, a Show more toggle for the rest), totals move into their own card, clickable cards dip when pressed, and a row of Sort by chips above the cards sorts them the way the headers do (tap to cycle, the arrow shows the direction). The stack layout lists receipt lines instead (pressable when rows are clickable) and the scroll layout keeps the table with its first column pinned.

Table is a Pro React component in the React table and list components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add table, together with Empty State, Skeleton, useControllableState, useLocale, useScrollEdges and 4 more and the npm packages 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 table

Table 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 Table from @/components/ui/table.

import { Table, type TableColumn } from "@/components/ui/table"

type Line = { id: string; product: string; qty: number; price: number; vat: number }

const lines: Line[] = [
  { id: "l1", product: "Northwind CRM Business license (yearly)", qty: 1, price: 1200, vat: 0.08 },
  { id: "l2", product: "Extra users", qty: 5, price: 120, vat: 0.08 },
  { id: "l3", product: "Setup and data migration", qty: 12, price: 95, vat: 0.08 },
]

const columns: TableColumn<Line>[] = [
  { key: "product", header: "Product / service", sortable: true, footer: ["Subtotal", "Sales tax 8%", "Total"] },
  { key: "qty", header: "Qty", type: "number" },
  { key: "price", header: "Unit price", type: "currency" },
  { key: "vat", header: "Tax", type: "percent" },
  {
    key: "total",
    header: "Total",
    type: "currency",
    sortable: true,
    accessor: (line) => line.qty * line.price,
    footer: ["$2,940.00", "$235.20", "$3,175.20"],
  },
]

export function InvoiceLines() {
  return <Table caption="Invoice #2026-0412" columns={columns} data={lines} locale="en-US" />
}

// parts, for tables the props API does not cover (same file, same look):
// TableRoot { density, variant, striped, stickyHeader, stickyFirstColumn } holds the context;
// TableScroll { maxHeight, fades, pinnedStart } scrolls with edge fades;
// TableHead { align, sort, onSort, pinned, width, minWidth, sortLabel, firstDirection } renders TableSortButton + aria-sort;
// TableRow { selected, clickable }, TableCell { align, pinned, truncate, numeric, wrap },
// TableEmpty { colSpan }, TableSkeleton { rows, columns, align };
// phone cards: TableCards { variant } > TableCard { interactive, selected } > TableCardHeader
// (TableCardTitle { onClick }, TableCardSubtitle, TableCardMeta, TableCardActions) + TableCardFields > TableCardField { label, wide };
// helpers: formatCellValue(value, type, { locale, currency }), compareValues(a, b, type, locale),
// TABLE_DENSITY, MOBILE_CARD, MOBILE_CARD_FIELDS (shared with data-table)
import {
  TableRoot, TableScroll, TableElement, TableCaption, TableHeader, TableBody,
  TableRow, TableHead, TableCell, formatCellValue,
} from "@/components/ui/table"

export function PriceList({ items }: { items: { id: string; name: string; price: number }[] }) {
  return (
    <TableRoot density="compact" variant="lined">
      <TableScroll>
        <TableElement>
          <TableCaption hidden>Price list</TableCaption>
          <TableHeader>
            <TableRow>
              <TableHead>Service</TableHead>
              <TableHead align="end">Price</TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            {items.map((item) => (
              <TableRow key={item.id}>
                <TableCell>{item.name}</TableCell>
                <TableCell align="end" numeric>{formatCellValue(item.price, "currency", { locale: "en-US" })}</TableCell>
              </TableRow>
            ))}
          </TableBody>
        </TableElement>
      </TableScroll>
    </TableRoot>
  )
}

Props

  • captiondefault ""
    ReactNode

    The table's name. It stays for screen readers even when hidden, and names the card list on phones. Always pass one. The playground falls back to the invoice number.

  • hideCaptiondefault true
    boolean

    Keeps the caption for screen readers only. Turn it off to show it under the table.

  • emptyTitledefault "No data"
    string

    Title of the default empty state (the playground's Clear button empties the invoice).

  • variantdefault "surface"
    "plain" | "surface" | "lined"

    surface is a raised panel with a hairline under the header; plain has no container and a tinted header band; lined keeps only the horizontal hairlines, for tables inside a card.

  • densitydefault "default"
    "compact" | "default" | "comfortable"

    Row height 36 / 48 / 56px with matching padding and type.

  • stripeddefault false
    boolean

    Tints every second row with a 2.5% foreground wash.

  • loadingdefault false
    boolean

    Skeleton rows (and skeleton cards on phones) in the real column layout, with aria-busy.

  • skeletonCountdefault 5
    number

    How many skeleton rows or cards show while loading.

  • showFooterdefault true
    boolean

    The footer row with each column's footer (totals). Automatic when any column has a footer; false hides it. On phones the totals become their own card.

  • stickyHeaderdefault false
    boolean

    The header sticks while rows scroll: to the page (offset by --table-sticky-top, e.g. var(--topbar-offset)), or to the table's own box with maxHeight.

  • stickyFirstColumndefault false
    boolean

    The first column stays put while scrolling sideways and casts a hairline shadow once rows slide under it.

  • maxHeightdefault 0
    number | string

    Caps the table's height (px or any CSS length); the rows scroll inside with a fade at the bottom. 0 means no cap.

  • mobileLayoutdefault "cards"
    "cards" | "scroll" | "stack"

    Below 768px: cards (each row a card), scroll (the table scrolls sideways with its first column pinned) or stack (label and value lines without card chrome, for receipts). Both layouts are in the server HTML, so nothing jumps.

  • mobileSortdefault true
    boolean

    Below 768px, a row of Sort by chips above the cards or stack, one per sortable column, since the headers are not there to press. Hidden when nothing is sortable.

  • cardVariantdefault "separate"
    "separate" | "grouped"

    Phone cards as separate raised cards, or one iOS-style grouped panel with hairlines.

  • localedefault ""
    string

    Intl locale for numbers, money and dates ("$12,480.00", "9/18/2026" in en-US). Empty uses the UIProvider's, then the browser's. The playground falls back to en-US.

  • currencydefault "USD"
    string

    ISO 4217 code for currency columns that do not set their own.

  • columns*
    TableColumn<T>[]

    { key, header, accessor?, cell?, type?: "text" | "number" | "currency" | "percent" | "date" | "datetime" | "boolean", currency?, align?, width?, minWidth?, sortable?, sortValue?, truncate?, footer?: ReactNode | ReactNode[], mobile?: "title" | "subtitle" | "meta" | "field" | "hidden", className?, headerClassName? }. Numbers end-align with tabular figures; cell always wins over the type. On phones the first column is the card title and the last numeric one its top-end value unless mobile says otherwise.

  • data*
    T[]

    The rows. A column without accessor reads row[key]; empty values show a muted dash.

  • getRowKeydefault "row.id ?? index"
    (row: T, index: number) => string

    A stable key per row. Duplicate keys log a warning in development.

  • sort
    { key: string; direction: "asc" | "desc" } | null

    Controlled sort. When set (null included), the table only draws the arrows and leaves sorting the data to you.

  • defaultSortdefault "null"
    { key: string; direction: "asc" | "desc" } | null

    Starting sort while uncontrolled; the table sorts on the client with a locale-aware collator.

  • onSortChange
    (sort: TableSort | null) => void

    Fires on every header press, controlled or not.

  • onRowClick
    (row: T) => void

    Makes rows clickable. The first cell renders a real button for the keyboard; clicks on controls inside the row (menus, links) never open it.

  • isRowSelected
    (row: T) => boolean

    Paints rows (and cards) as selected. Behavior stays with you; data-table owns selection.

  • rowActions
    (row: T) => ReactNode

    A trailing cell on desktop (revealed on hover or focus, always on touch) and the card's top-end slot on phones, typically a DropdownMenu with a "⋯" trigger.

  • renderCard
    (row: T, index: number) => ReactNode

    Your own phone card, built from the TableCard parts.

  • emptyStatedefault "<EmptyState size="sm" />"
    ReactNode

    Replaces the default empty state, spanning every column.

  • labels
    Partial<TableLabels>

    Strings: sortAscending / sortDescending / clearSort ("Sort by {column}, ascending"), empty, actions, showMore, showLess, yes, no, totals, sortBy. DEFAULT_TABLE_LABELS holds the English defaults.

  • classNames
    Partial<Record<"root" | "scroll" | "table" | "caption" | "header" | "head" | "body" | "row" | "cell" | "footer" | "empty" | "cards" | "card" | "cardTitle" | "cardFields" | "stack", string>>

    Extra classes for the inner parts.

  • className
    string

    Extra classes for the outer wrapper that holds the table and its phone layout.

Parts

  • TableRoot

    component

    The outer part of a hand-built table: holds density, variant, stripes and sticky settings for every part inside.

    • densitydefault "default"
      "compact" | "default" | "comfortable"

      Row height 36 / 48 / 56px with matching cell padding.

    • variantdefault "surface"
      "plain" | "surface" | "lined"

      A raised surface, a plain table, or hairlines only.

    • stripeddefault false
      boolean

      Tints every other body row.

    • stickyHeaderdefault false
      boolean

      Keeps the header row at the top of TableScroll.

    • stickyFirstColumndefault false
      boolean

      Keeps the first column in view while scrolling sideways.

  • TableScroll

    component

    The scroll box around the table element, with fades toward any edge that has more to scroll.

    • maxHeight
      number | string

      Caps the height; the body scrolls inside and a sticky header sticks to this box.

    • fadesdefault true
      boolean

      Fades the content out toward an edge with more to scroll.

    • pinnedStart
      boolean

      Keeps the first column out of the left fade.

  • TableElement

    component

    The native table element: full width, separated borders without spacing, caption at the bottom.

  • TableCaption

    component

    The table's caption. hidden keeps it for screen readers only.

    • hiddendefault false
      boolean

      Visually hidden, still names the table.

  • TableHeader

    component

    The thead section; rows inside it take the header look.

  • TableBody

    component

    The tbody section; rows inside it get the density height, hairlines, stripes and the hover tint when clickable.

  • TableFooter

    component

    The tfoot section for totals, with a top rule and medium weight.

  • TableRow

    component

    A row. selected tints it and sets aria-selected; an onClick (or clickable) gives it the pressable look.

    • selecteddefault false
      boolean

      Selected tint and aria-selected.

    • clickable
      boolean

      Forces the interactive look without an onClick.

  • TableHead

    component

    A header cell. With onSort it renders a sort button and sets aria-sort.

    • aligndefault "start"
      "start" | "center" | "end"

      Text alignment; end for numbers.

    • sortdefault false
      "asc" | "desc" | false

      The current sort of this column.

    • onSort
      () => void

      Makes the header a sort button.

    • pinneddefault false
      boolean | "phone"

      Sticks to the start while scrolling sideways; phone pins below 768px only.

    • width
      number | string

      Column width.

    • minWidth
      number | string

      Column minimum width.

    • sortLabel
      string

      The sort button's aria-label; defaults to the header text.

    • firstDirection
      "asc" | "desc"

      The direction the first press sorts in.

  • TableSortButton

    component

    The sort button TableHead renders, with the direction arrow and an aria-label built from the labels; use it alone in a custom header.

  • TableCell

    component

    A body cell with alignment, pinning, truncation and tabular numbers.

    • aligndefault "start"
      "start" | "center" | "end"

      Text alignment.

    • pinneddefault false
      boolean | "phone"

      Sticks to the start while scrolling sideways.

    • truncatedefault false
      boolean

      One line with an ellipsis; string content also goes into a title.

    • numericdefault false
      boolean

      Tabular numbers.

    • wrapdefault false
      boolean

      Lets the text wrap instead of scrolling sideways.

  • TableEmpty

    component

    A full-width row for an empty state; colSpan is the column count.

  • TableSkeleton

    component

    Placeholder rows with shimmering bars while data loads.

    • rowsdefault 5
      number

      How many rows.

    • columnsdefault 4
      number | (number | string)[]

      A count, or one width hint per column.

    • align
      ("start" | "center" | "end")[]

      Aligns each column's bar.

  • TableCards

    component

    The phone list that replaces the table below 768px: separate raised cards, or one grouped panel with hairlines (variant).

    • variantdefault "separate"
      "separate" | "grouped"

      Raised cards with gaps, or one iOS-style grouped panel.

  • TableCard

    component

    One phone card. interactive adds the press feedback and focus ring of its title button; selected tints it.

    • interactivedefault false
      boolean

      Pressable look.

    • selecteddefault false
      boolean

      Selected tint.

  • TableCardHeader

    component

    The top row of a card: title block on the left, meta or actions on the right.

  • TableCardTitle

    component

    The card title, clamped to two lines. With onClick it becomes a button whose hit area covers the whole card.

  • TableCardSubtitle

    component

    The muted line under the card title.

  • TableCardMeta

    component

    The right side of the card header, for an amount in tabular numbers or a status badge.

  • TableCardActions

    component

    Buttons inside a card that stay clickable above the card's stretched title button.

  • TableCardFields

    component

    A two-column definition list of the remaining fields.

  • TableCardField

    component

    One label and value in TableCardFields; wide spans both columns.

    • label*
      ReactNode

      The field name.

    • widedefault false
      boolean

      Spans both columns.

  • formatCellValue

    function

    A cell value as text in the locale, the way the props API prints it: "$12,480.00", "19%" (from 0.19), "9/18/2026". Returns "" for empty values. Signature: (value: unknown, type?: TableColumnType, options?: { locale?: string; currency?: string }) => string.

  • compareValues

    function

    The sort comparator of the props API: empty values last, numbers by value, dates by time, text by Intl.Collator. Signature: (a: unknown, b: unknown, type?: TableColumnType, locale?: string) => number.

  • DEFAULT_TABLE_LABELS

    constant

    The English strings (sort announcements, empty, actions, show more / less, yes / no, totals, sort by). Spread it to translate a few. Type: TableLabels.

  • TABLE_DENSITY

    constant

    The row, cell and header classes per density, shared with data-table so both tables line up.

  • MOBILE_CARD

    constant

    The class string of a phone card (rounded surface, padding, raised shadow), shared with data-table and file-list.

  • MOBILE_CARD_FIELDS

    constant

    The class string of the two-column field grid inside a phone card.

  • TableColumn

    type

    One column of the props API: key, header, accessor or cell, type, currency, align, width, sortable, sortValue, truncate, wrap, footer and its role on a phone card (mobile).

Examples

Clients with row actions

The 12 canonical clients: clickable rows, status badges, a ⋯ menu per row, sorted by balance; cards on a phone.

Price list inside a card

variant="lined" keeps only hairlines so the table sits in a Card; a receipt-style stack on phones.

Compact with a sticky header

40 orders, density="compact", maxHeight 320 and stickyHeader; the rows scroll under the header.

Pinned first column

Ten months of revenue per city with stickyFirstColumn, striped rows, totals and mobileLayout="scroll".

Loading and empty

Skeleton rows in the real column layout while loading, and a custom empty state spanning every column.

Receipt stack on phones

mobileLayout="stack": label and value lines with the totals under a dashed rule, like a receipt.

Parts API: plan comparison

Hand-built from TableRoot, TableHead and TableCell: check marks, a highlighted column, the first column pinned on phones.

Dependencies

motionlucide-reactempty-stateskeletonuse-controllable-stateuse-localeuse-scroll-edgesdate-utilsmotionnumber-formatui-config

Files

  • components/ui/table.tsx

FAQ

Is the React Table component free?

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

How do I install Table?

Run npx wingo-ui@latest add table. It also adds the Wingo UI items it builds on (Empty State, Skeleton, useControllableState, useLocale, useScrollEdges and 4 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/table.

How does Table behave on phones?

On a phone every row becomes a card (title, amount at the top end, up to four label and value pairs, a Show more toggle for the rest), totals move into their own card, clickable cards dip when pressed, and a row of Sort by chips above the cards sorts them the way the headers do (tap to cycle, the arrow shows the direction).

See also

Data TableDescription ListEmpty StateSkeletonPaginationCard

Used in

Data TableDeal PipelinePricing tableCSV ImportAudit LogMembers tableAdmin Dashboard PageAPI Keys PageAutomation Builder PageCRM Contact PageKPI DashboardSettings Page

and 1 more.

More React table and list components

Task ListVirtual ListKanbanEditable GridListPermissions Matrix
All 11 React table and list components

Articles using this component

  • React Virtualized Table: 100k Rows With TanStack Virtual

    Component guides · Oct 16, 2026 · 12 min read

  • Responsive Table Mobile Layout: Rows to Cards in React

    Mobile UI in React · Oct 9, 2026 · 11 min read

  • TanStack Table v9 Data Table: Filters, Bulk Actions, Mobile

    Component guides · Oct 9, 2026 · 12 min read

PreviousPermissions MatrixNext Task List

Invoice #2026-0412Issued

Tech Solutions Inc. · due 10/18/2026

Invoice #2026-0412
QtyUnit priceTax
1 pcs$1,200.008%$1,200.00
5 pcs$120.008%$600.00
12 h$95.008%$1,140.00
1 pcs$300.008%$300.00
2 sessions$150.008%$300.00
Subtotal$3,540.00
Sales tax 8%$283.20
Amount due$3,823.20
Sort by
  • Northwind CRM Business licenseYearly plan, 10 users
    $1,200.00
    Qty
    1 pcs
    Unit price
    $1,200.00
  • Extra usersYearly license per user
    $600.00
    Qty
    5 pcs
    Unit price
    $120.00
  • Setup and data migrationImport clients, invoices and products
    $1,140.00
    Qty
    12 h
    Unit price
    $95.00
  • QuickBooks integrationSetup and a test sync
    $300.00
    Qty
    1 pcs
    Unit price
    $300.00
  • Team training2-hour online session
    $300.00
    Qty
    2 sessions
    Unit price
    $150.00
Subtotal
$3,540.00
Sales tax 8%
$283.20
Amount due
$3,823.20

Sort by Product or Total; click a row to select it