Components

customeditabletable

An inline-editable tabular grid where each cell is a typed input (text, number, email, date, select); use for structured data with a fixed or user-managed list of rows and known columns.

An inline-editable tabular grid where each cell is a typed input (text, number, email, date, or select). Use when structured data has a fixed or user-managed list of rows and a known set of typed columns — such as product inventories, fee schedules, or employee contact tables.

Read Form Rules before using this component — keys, labels, required fields, expressions, visibility, and validation all follow shared conventions.

When to use

  • Tabular data entry with a known set of typed columns (text, number, email, date, select)
  • Fixed rows known at design time (e.g. a list of product categories the user fills in)
  • User-managed rows the officer can add and delete at runtime (e.g. fee schedules, line items)
  • Grids that need column-level validation (required, min/max, pattern) without rendering full Formly fields per cell

When NOT to use

Instead of customeditabletable, use…For…
customdatatableRead-only display of integration-gateway data
customdatasettableRead-only display of model data or direct URL fetches; row click selection
customitemselectiontablePer-row Add/Remove selection workflows
customeditableinputA single inline-editable cell input used as a column primitive (not a full table)
customcascadingeditableinputA cascading cell input where one column's options depend on another column's value within a row
A regular Formly repeaterRows with deeply nested or heterogeneous fields that don't fit a uniform cell grid

Props

Component-level props

PropTypeRequired?Description
labelstringoptionalLabel displayed above the table.
rowsarrayrequiredArray of row configurations — see row shape below; omit content when allowRowAddition: true (user creates rows).
columnsarrayrequiredArray of column configurations — see column shape below.
rowColumnTitlestringoptionalHeader text for the row-label column; only shown when showRowLabels: true.
showRowLabelsbooleanoptionalWhen true, displays a leading column with each row's label.
allowRowAdditionbooleanoptionalWhen true, shows an "Add Row" button so the user can append rows at runtime; default false.
allowRowDeletionbooleanoptionalWhen true, shows a delete button on each row; default false.
disabledbooleanoptionalWhen true, all cell inputs are read-only.
customTableClassstringoptionalCSS class applied to the table element for custom styling.

Row configuration (each item in rows)

KeyTypeRequired?Description
keystringrequiredUnique identifier for the row — used as the key in the returned data object.
labelstringrequiredDisplay label shown in the row-label column.
disabledbooleanoptionalWhen true, all inputs in this row are disabled.

Column configuration (each item in columns)

KeyTypeRequired?Description
keystringrequiredUnique identifier for the column — used as the field name in each row's data.
labelstringrequiredColumn header text.
typestringoptionalCell input type: "text" (default), "number", "email", "date", "select".
requiredbooleanoptionalWhen true, the cell must not be empty.
disabledbooleanoptionalWhen true, all cells in this column are read-only.
placeholderstringoptionalPlaceholder text shown inside the cell input.
widthstringoptionalCSS width for the column — e.g. "150px" or "20%".
minnumberoptionalMinimum value — number type only.
maxnumberoptionalMaximum value — number type only.
patternstringoptionalRegex validation pattern — text type only.
optionsarrayrequired (select)For select type — array of { "label": string, "value": any }.

Column types

TypeInput widgetNotes
"text"Text inputSupports pattern validation
"number"Numeric inputSupports min / max; value stored as number
"email"Email inputBuilt-in email format validation
"date"Date pickerReturns ISO date string (YYYY-MM-DD)
"select"Searchable dropdownRequires options array

Data structure

The component returns an object keyed by row.key, each holding an object keyed by column.key:

{
  "row1": { "quantity": 100, "price": 5000, "status": "available" },
  "row2": { "quantity": 50, "price": 7500, "status": "out_of_stock" }
}

Examples

Fixed rows, two number columns

A simple inventory table with fixed rows and no add/delete.

{
  "key": "INVENTORY",
  "type": "customeditabletable",
  "props": {
    "label": "Product Inventory",
    "rowColumnTitle": "Product",
    "showRowLabels": true,
    "rows": [
      { "key": "PRODUCT_1", "label": "Laptop" },
      { "key": "PRODUCT_2", "label": "Mouse" }
    ],
    "columns": [
      {
        "key": "QUANTITY",
        "label": "Quantity",
        "type": "number",
        "required": true,
        "min": 0
      },
      {
        "key": "PRICE",
        "label": "Price (RWF)",
        "type": "number",
        "required": true,
        "min": 0
      }
    ]
  },
  "validation": {
    "messages": {
      "required": "This field is required."
    }
  }
}

Dynamic rows with mixed column types

A fee schedule where officers add and delete rows; includes a text column with regex, a number column, and a status select.

{
  "key": "FEE_SCHEDULE",
  "type": "customeditabletable",
  "props": {
    "label": "Fee Schedule",
    "allowRowAddition": true,
    "allowRowDeletion": true,
    "rows": [],
    "columns": [
      {
        "key": "SERVICE_NAME",
        "label": "Service Name",
        "type": "text",
        "required": true,
        "placeholder": "Enter service name",
        "width": "40%",
        "pattern": "^[a-zA-Z0-9\\s\\-]+$"
      },
      {
        "key": "FEE",
        "label": "Fee (RWF)",
        "type": "number",
        "required": true,
        "min": 0,
        "placeholder": "Enter fee",
        "width": "25%"
      },
      {
        "key": "STATUS",
        "label": "Status",
        "type": "select",
        "required": true,
        "width": "25%",
        "options": [
          { "label": "Active", "value": "ACTIVE" },
          { "label": "Inactive", "value": "INACTIVE" }
        ],
        "placeholder": "Select status"
      }
    ]
  },
  "validation": {
    "messages": {
      "required": "This field is required.",
      "min": "Value must be 0 or greater.",
      "pattern": "Service name contains invalid characters."
    }
  }
}

Common mistakes

  • Setting rows: [] when allowRowAddition: false — the table renders with no rows and the user has no way to add any; either provide a rows array with at least one item or set allowRowAddition: true.
  • Omitting options on a select-type column — the dropdown renders empty.
  • Using min / max on a text-type column — these constraints only apply to number type; they are silently ignored.
  • Using pattern on a number-type column — pattern only applies to text; use min and max for numeric constraints.
  • Setting required: true at the component level — required on the table itself is not supported; mark individual columns as required: true.
  • Omitting rowColumnTitle when showRowLabels: true — the row-label column header is blank.
  • Reusing the same key across two columns — the returned data object overwrites one column's value; every column key must be unique within the table.

Checklist

  • key is UPPER_SNAKE_CASE and unique across the entire form
  • props.label is present, unique, and correctly cased
  • Every columns[].key and rows[].key is unique within the table
  • select-type columns include an options array
  • min/max are only used on number columns
  • pattern is only used on text columns
  • Cell-level required: true has a matching validation.messages.required
  • min/max/pattern constraints each have matching validation messages
  • allowRowAddition: false and a non-empty rows array, or allowRowAddition: true (never empty rows with no add button)
  • showRowLabels: true is paired with a rowColumnTitle
  • Not using customeditabletable when the data is read-only (use customdatatable or customdatasettable)

On this page