Skip to main content

ProductSpecifications

Renders product attributes/specifications as a table or stacked list, with optional grouping by attribute group. The component can fetch attributes autonomously via the SDK or accept pre-fetched data.


Usage

Self-contained: fetch by product ID

import ProductSpecifications from '@/components/propeller/ProductSpecifications';
import { graphqlClient } from '@/lib/graphql';

<ProductSpecifications
graphqlClient={graphqlClient}
productId={12345}
language="EN"
/>

Pre-fetched attributes (no SDK client needed)

import ProductSpecifications from '@/components/propeller/ProductSpecifications';

// attributes already available from a parent query
<ProductSpecifications
attributes={product.attributeResults}
language="EN"
/>

List layout

<ProductSpecifications
graphqlClient={graphqlClient}
productId={12345}
layout="list"
/>

Grouped by attribute group

<ProductSpecifications
graphqlClient={graphqlClient}
productId={12345}
grouping={true}
/>

Grouped list layout with custom class

<ProductSpecifications
graphqlClient={graphqlClient}
productId={12345}
layout="list"
grouping={true}
className="mt-8"
/>

Static attributes without grouping (e.g., quick-view modal)

<ProductSpecifications
attributes={selectedProduct.attributeResults}
language="NL"
layout="list"
className="text-sm"
/>

Configuration

Data

PropTypeDefaultDescription
graphqlClientGraphQLClientundefinedInitialized Propeller SDK client. Required when productId is set.
productIdnumberundefinedProduct ID to fetch attributes for. When set, the component fetches its own data and ignores attributes.
attributesAttributeResult[]undefinedPre-fetched attribute results. Used as fallback when productId is not provided.

Display

PropTypeDefaultDescription
layout'table' | 'list''table''table' renders a two-column table (name / value). 'list' renders vertically stacked label + value rows.
groupingbooleanfalseWhen true, groups attributes by their attributeDescription.group field and renders a heading per section.

Localization

PropTypeDefaultDescription
languagestring'NL'Language code used to resolve localized attribute labels and text-type values.

Styling

PropTypeDefaultDescription
classNamestringundefinedExtra CSS class applied to the root wrapper element.

Behavior

  • Auto-hide when empty: The component renders nothing when there are no public attributes or while loading. There is no empty-state message.
  • Filtering: Only attributes where attributeDescription.isPublic === true are shown. Attributes with empty, null, or '0' values are excluded.
  • Data priority: When productId is provided, the component fetches its own data and ignores the attributes prop entirely. When productId is absent, it falls back to attributes.
  • Re-fetch on product change: Changing the productId prop triggers a new fetch automatically.
  • Localization resolution: For labels, the component searches attributeDescription.descriptions for a matching language entry. If none is found, it falls back to attributeDescription.name. For TEXT-type values, it filters textValues by language.
  • Attribute type handling: Each attribute type (TEXT, ENUM, INT, DECIMAL, DATETIME, COLOR) has dedicated extraction logic. Unknown types fall back to value.value, with booleans rendered as "Yes"/"No".
  • Group ordering: Groups appear in the order they are first encountered in the attribute list (insertion order, not alphabetical).
  • Group headings: In grouped mode, a heading is rendered for each group that has a non-empty group name. Attributes with no group are rendered without a heading.
  • Loading state: A loading flag is set during fetch but no spinner is shown; the component simply renders nothing until data arrives.
  • Error handling: Fetch errors are caught silently. The component remains hidden if no data is available.

GraphQL Query

If you need to fetch product specifications/attributes outside the component (e.g., in a server component or parent query), use the following query structure:

query ProductAttributes($productId: Int!) {
product(id: $productId) {
attributeResults(
filter: { attributeDescription: { isPublic: true } }
page: 1
offset: 100
) {
items {
attributeDescription {
name
isPublic
group
descriptions {
language
value
}
}
value {
type
... on AttributeTextValue {
textValues {
language
values
}
}
... on AttributeEnumValue {
enumValues
}
... on AttributeIntValue {
intValue
}
... on AttributeDecimalValue {
decimalValue
}
... on AttributeDateTimeValue {
dateTimeValue
}
... on AttributeColorValue {
colorValue
}
}
}
}
}
}

Pass the returned items array to the attributes prop:

<ProductSpecifications attributes={data.product.attributeResults.items} />

SDK Services

The component uses ProductService.getAttributeResultByProductId() to fetch attributes when productId is provided.

Product fields read

The component works with the AttributeResult type from the SDK. Each attribute result contains:

Field pathUsage
attributeDescription.isPublicFilters to only public attributes
attributeDescription.nameFallback label when no localized description matches
attributeDescription.descriptions[]Array of LocalizedString objects; matched by language to resolve the display label
attributeDescription.groupUsed to section attributes when grouping={true}
value.typeDetermines how to extract the display value (TEXT, ENUM, INT, DECIMAL, DATETIME, COLOR)
value.textValues[]For TEXT type: array with language and values fields
value.enumValues[]For ENUM type: string array
value.intValueFor INT type: numeric value
value.decimalValueFor DECIMAL type: numeric value
value.dateTimeValueFor DATETIME type: string value
value.colorValueFor COLOR type: string value
value.valueGeneric fallback; booleans render as "Yes"/"No"

Fetch parameters

When fetching autonomously, the component calls:

service.getAttributeResultByProductId(productId, {
attributeDescription: { isPublic: true },
page: 1,
offset: 2000,
});

This requests up to 2000 public attributes in a single call.