This guide covers best practices for displaying SmartMenu search results, match badges, and nutrition information in your UI.
Match Groups
Search results are grouped into three categories based on how well dishes match the guest’s preferences.
Match Status Overview
Status Badge Color When to Show MATCHMatch Green Always prominent ALMOST_MATCHAlmost Yellow/Orange With warnings NOT_MATCHNot a Match Grey Optional, dimmed
Visual Hierarchy
Match Badges
Badge Components
function MatchBadge ({ status }) {
const config = {
MATCH: { label: 'Match' , color: 'green' , icon: '✓' },
ALMOST_MATCH: { label: 'Almost' , color: 'yellow' , icon: '⚠' },
NOT_MATCH: { label: 'Not a Match' , color: 'grey' , icon: '✗' }
};
const { label , color , icon } = config [ status ];
return (
< span className = { `badge badge- ${ color } ` } >
{ icon } { label }
</ span >
);
}
CSS Styling
.badge {
display : inline-flex ;
align-items : center ;
padding : 4 px 8 px ;
border-radius : 4 px ;
font-size : 12 px ;
font-weight : 600 ;
}
.badge-green {
background : #d1fae5 ;
color : #065f46 ;
}
.badge-yellow {
background : #fef3c7 ;
color : #92400e ;
}
.badge-grey {
background : #f3f4f6 ;
color : #6b7280 ;
}
Match Reasons
For ALMOST_MATCH and NOT_MATCH dishes, always display the reasons:
function DishCard ({ dish , matchStatus , matchReasons }) {
return (
< div className = { `dish-card ${ matchStatus . toLowerCase () } ` } >
< img src = { dish . imageUrl } alt = { dish . name } />
< h3 > { dish . name } </ h3 >
< MatchBadge status = { matchStatus } />
{ matchReasons && matchReasons . length > 0 && (
< div className = "match-reasons" >
{ matchReasons . map (( reason , i ) => (
< span key = { i } className = "reason" > ⚠ { reason } </ span >
)) }
</ div >
) }
< div className = "nutrition-summary" >
{ dish . nutrition . calories } cal
</ div >
</ div >
);
}
Compact Dish View
For list layouts, show key information inline:
function DishListItem ({ dish , matchStatus }) {
return (
< div className = "dish-list-item" >
< img src = { dish . imageUrl } alt = { dish . name } />
< div className = "dish-info" >
< div className = "dish-header" >
< h3 > { dish . name } </ h3 >
< MatchBadge status = { matchStatus } />
</ div >
< div className = "dish-meta" >
{ dish . nutrition . calories } cal • { dish . nutrition . protein } g protein
{ dish . diets . map ( d => < span key = { d . type } > • { d . displayName } </ span > ) }
</ div >
{ dish . allergens . length > 0 && (
< div className = "allergens" >
Contains: { dish . allergens . map ( a => a . displayName ). join ( ', ' ) }
</ div >
) }
</ div >
< button className = "order-btn" > Order </ button >
</ div >
);
}
Nutrition Panel
Full Nutrition Facts
Display the complete nutrition panel in dish detail views:
function NutritionPanel ({ nutrition }) {
return (
< div className = "nutrition-panel" >
< h4 > Nutrition Facts </ h4 >
< NutritionRow label = "Calories" value = { nutrition . calories } bold />
< div className = "divider" />
< NutritionRow label = "Total Fat" value = { ` ${ nutrition . fatTotal } g` } bold />
< NutritionRow label = "Saturated Fat" value = { ` ${ nutrition . fatSaturated } g` } indent />
< NutritionRow label = "Trans Fat" value = { ` ${ nutrition . fatTrans } g` } indent />
< NutritionRow label = "Cholesterol" value = { ` ${ nutrition . cholesterol } mg` } bold />
< NutritionRow label = "Sodium" value = { ` ${ nutrition . sodium } mg` } bold />
< NutritionRow label = "Total Carbohydrates" value = { ` ${ nutrition . carbohydrates } g` } bold />
< NutritionRow label = "Dietary Fiber" value = { ` ${ nutrition . dietaryFiber } g` } indent />
< NutritionRow label = "Sugars" value = { ` ${ nutrition . sugar } g` } indent />
< NutritionRow label = "Protein" value = { ` ${ nutrition . protein } g` } bold />
</ div >
);
}
Allergen Badges
Allergen information is critical for guest safety. Make allergen badges highly visible and never hide them.
function AllergenBadges ({ allergens }) {
const icons = {
DAIRY: '🥛' ,
EGG: '🥚' ,
FISH: '🐟' ,
SHELLFISH: '🦐' ,
TREE_NUT: '🌰' ,
PEANUT: '🥜' ,
WHEAT: '🌾' ,
SOY: '🫛' ,
SESAME: '⚪'
};
return (
< div className = "allergen-badges" role = "alert" >
< span className = "allergen-label" > Contains: </ span >
{ allergens . map ( allergen => (
< span key = { allergen . type } className = "allergen-badge" title = { allergen . source } >
{ icons [ allergen . type ] } { allergen . displayName }
</ span >
)) }
</ div >
);
}
function DietTags ({ diets }) {
return (
< div className = "diet-tags" >
{ diets . map ( diet => (
< span key = { diet . type } className = "diet-tag" >
{ diet . displayName }
</ span >
)) }
</ div >
);
}
Empty States
Handle cases where no dishes match:
function EmptyMatchesState ({ preferences }) {
return (
< div className = "empty-state" >
< h3 > No exact matches found </ h3 >
< p >
We couldn't find dishes that match all your preferences.
Try adjusting your filters or check out the "Almost Matches" below.
</ p >
< button onClick = { () => clearFilters () } >
Clear Filters
</ button >
</ div >
);
}
Accessibility
Screen Readers Use aria-label on badges: “Match status: Almost match, contains dairy”
Color Contrast Ensure badge colors meet WCAG AA contrast ratios
Keyboard Navigation All interactive elements should be keyboard accessible
Focus Indicators Visible focus states on dish cards and buttons
// Accessible badge example
< span
className = "badge badge-yellow"
role = "status"
aria-label = { `Match status: Almost match. ${ matchReasons . join ( '. ' ) } ` }
>
⚠ Almost
</ span >