Loading...
Loading...
Apply when building React components under react/ or configuring store blocks in store/ for VTEX IO apps. Covers interfaces.json, contentSchemas.json for Site Editor, VTEX Styleguide for admin apps, and css-handles for storefront styling. Use for creating custom storefront components, admin panels, pixel apps, or any frontend development within the VTEX IO react builder ecosystem.
npx skill4agent add vtexdocs/ai-skills vtex-io-react-appsreactinterfaces.jsoncontentSchemas.jsoncontentSchemas.jsoncss-handlesvtex-io-service-appsvtex-io-graphql-apivtex-io-app-structureinterfaces.json/store"component"/react"allowed""composition""children""blocks"/react"component": "ProductReviews"react/ProductReviews.tsxvtex.css-handlesvtex.styleguidecontentSchemas.json/storereact-intlmessagesuseQueryreact-apolloStore Theme (JSON blocks)
└── declares "product-reviews" block with props
│
▼
interfaces.json → maps "product-reviews" to "ProductReviews" component
│
▼
react/ProductReviews.tsx → React component renders
│
├── useCssHandles() → CSS classes for styling
├── useQuery() → GraphQL data fetching
└── useProduct() / useOrderForm() → Store Framework context hooksstore/interfaces.jsoninterfaces.json/reactstore/interfaces.json{
"product-reviews": {
"component": "ProductReviews",
"composition": "children",
"allowed": ["product-review-item"]
},
"product-review-item": {
"component": "ReviewItem"
}
}// react/ProductReviews.tsx
import ProductReviews from './components/ProductReviews'
export default ProductReviews// react/ProductReviews.tsx exists but NO store/interfaces.json entry
// The component compiles fine but cannot be used in any theme.
// Adding <product-reviews /> in a theme JSON will produce:
// "Block 'product-reviews' not found"
import ProductReviews from './components/ProductReviews'
export default ProductReviewsadminvtex.styleguide@material-ui@chakra-ui/react@chakra-uiantd@ant-designvtex.styleguide// react/admin/ReviewModeration.tsx
import React, { useState } from 'react'
import {
Layout,
PageHeader,
Table,
Button,
Tag,
Modal,
Input,
} from 'vtex.styleguide'
interface Review {
id: string
author: string
rating: number
text: string
status: 'pending' | 'approved' | 'rejected'
}
function ReviewModeration() {
const [reviews, setReviews] = useState<Review[]>([])
const [modalOpen, setModalOpen] = useState(false)
const tableSchema = {
properties: {
author: { title: 'Author', width: 200 },
rating: { title: 'Rating', width: 100 },
text: { title: 'Review Text' },
status: {
title: 'Status',
width: 150,
cellRenderer: ({ cellData }: { cellData: string }) => (
<Tag type={cellData === 'approved' ? 'success' : 'error'}>
{cellData}
</Tag>
),
},
},
}
return (
<Layout fullWidth pageHeader={<PageHeader title="Review Moderation" />}>
<Table
items={reviews}
schema={tableSchema}
density="medium"
/>
</Layout>
)
}
export default ReviewModeration// react/admin/ReviewModeration.tsx
import React from 'react'
import { DataGrid } from '@material-ui/data-grid'
import { Button } from '@material-ui/core'
// Material UI components will look inconsistent in the VTEX Admin,
// conflict with global styles, and inflate bundle size.
// This app will fail VTEX App Store review.
function ReviewModeration() {
return (
<div>
<DataGrid rows={[]} columns={[]} />
<Button variant="contained" color="primary">Approve</Button>
</div>
)
}/reactcomponentinterfaces.json/reactinterfaces.json"component": "ProductReviews"react/ProductReviews.tsxinterfaces.json/react// react/ProductReviews.tsx — root-level export file
import ProductReviews from './components/ProductReviews/index'
export default ProductReviews// react/components/ProductReviews/index.tsx — actual implementation
import React from 'react'
import { useCssHandles } from 'vtex.css-handles'
const CSS_HANDLES = ['container', 'title', 'list'] as const
interface Props {
title: string
maxReviews: number
}
function ProductReviews({ title, maxReviews }: Props) {
const handles = useCssHandles(CSS_HANDLES)
return (
<div className={handles.container}>
<h2 className={handles.title}>{title}</h2>
{/* ... */}
</div>
)
}
export default ProductReviewsreact/components/ProductReviews/index.tsx exists but
react/ProductReviews.tsx does NOT exist.
The builder cannot find the component.
Error: "Could not find component ProductReviews"// react/components/ProductReviews/index.tsx
import React, { useMemo } from 'react'
import { useQuery } from 'react-apollo'
import { useProduct } from 'vtex.product-context'
import { useCssHandles } from 'vtex.css-handles'
import GET_REVIEWS from '../../graphql/getReviews.graphql'
import ReviewItem from './ReviewItem'
const CSS_HANDLES = [
'reviewsContainer',
'reviewsTitle',
'reviewsList',
'averageRating',
'emptyState',
] as const
interface Props {
title?: string
showAverage?: boolean
maxReviews?: number
}
function ProductReviews({
title = 'Customer Reviews',
showAverage = true,
maxReviews = 10,
}: Props) {
const handles = useCssHandles(CSS_HANDLES)
const productContext = useProduct()
const productId = productContext?.product?.productId
const { data, loading, error } = useQuery(GET_REVIEWS, {
variables: { productId, limit: maxReviews },
skip: !productId,
})
const averageRating = useMemo(() => {
if (!data?.reviews?.length) return 0
const sum = data.reviews.reduce(
(acc: number, review: { rating: number }) => acc + review.rating,
0
)
return (sum / data.reviews.length).toFixed(1)
}, [data])
if (loading) return <div className={handles.reviewsContainer}>Loading...</div>
if (error) return null
return (
<div className={handles.reviewsContainer}>
<h2 className={handles.reviewsTitle}>{title}</h2>
{showAverage && data?.reviews?.length > 0 && (
<div className={handles.averageRating}>
Average: {averageRating} / 5
</div>
)}
{data?.reviews?.length === 0 ? (
<p className={handles.emptyState}>No reviews yet.</p>
) : (
<ul className={handles.reviewsList}>
{data.reviews.map((review: { id: string; author: string; rating: number; text: string }) => (
<ReviewItem key={review.id} review={review} />
))}
</ul>
)}
</div>
)
}
export default ProductReviews// react/ProductReviews.tsx
import ProductReviews from './components/ProductReviews'
export default ProductReviews{
"product-reviews": {
"component": "ProductReviews",
"composition": "children",
"allowed": ["product-review-form"],
"render": "client"
}
}{
"definitions": {
"ProductReviews": {
"type": "object",
"properties": {
"title": {
"type": "string",
"title": "Section Title",
"description": "Title displayed above the reviews list",
"default": "Customer Reviews"
},
"showAverage": {
"type": "boolean",
"title": "Show average rating",
"default": true
},
"maxReviews": {
"type": "number",
"title": "Maximum reviews",
"default": 10,
"enum": [5, 10, 20, 50]
}
}
}
}
}{
"store.product": {
"children": [
"product-images",
"product-name",
"product-price",
"buy-button",
"product-reviews"
]
},
"product-reviews": {
"props": {
"title": "What Our Customers Say",
"showAverage": true,
"maxReviews": 20
}
}
}@material-ui/core@chakra-ui/reactantdvtex.styleguidefetch()axiosuseQueryreact-apollomessagesreact-intlinterfaces.json"component": "ProductReviews"react/ProductReviews.tsxstore/interfaces.jsoninterfaces.json/reactvtex.styleguidecss-handlesuseQueryreact-intlmessagescontentSchemas.json