Semantic Diff
feedbackCustom
Grouped list of spec changes (added, removed, modified, moved, renamed) with a category badge and human-readable description per change.
Custom component — no upstream reference.
Variants
Mixed Changes
Personal Information
- ADDEDAdded field: Middle name
Contact Details
- MODIFIEDModified field: Phone (type changed from text to tel)
- REMOVEDRemoved field: Fax number
Identity
- RENAMEDRenamed: "SSN" → "Social Security Number"
<section class="flex-semantic-diff">
<div class="flex-semantic-diff__group" data-group="personal-info">
<h3 class="flex-semantic-diff__heading">Personal Information</h3>
<ul class="flex-semantic-diff__list">
<li class="flex-semantic-diff__change" data-category="added"><span class="flex-semantic-diff__badge">ADDED</span><span class="flex-semantic-diff__description">Added field: Middle name</span></li>
</ul>
</div>
<div class="flex-semantic-diff__group" data-group="contact">
<h3 class="flex-semantic-diff__heading">Contact Details</h3>
<ul class="flex-semantic-diff__list">
<li class="flex-semantic-diff__change" data-category="modified"><span class="flex-semantic-diff__badge">MODIFIED</span><span class="flex-semantic-diff__description">Modified field: Phone (type changed from text to tel)</span></li>
<li class="flex-semantic-diff__change" data-category="removed"><span class="flex-semantic-diff__badge">REMOVED</span><span class="flex-semantic-diff__description">Removed field: Fax number</span></li>
</ul>
</div>
<div class="flex-semantic-diff__group" data-group="identity">
<h3 class="flex-semantic-diff__heading">Identity</h3>
<ul class="flex-semantic-diff__list">
<li class="flex-semantic-diff__change" data-category="renamed"><span class="flex-semantic-diff__badge">RENAMED</span><span class="flex-semantic-diff__description">Renamed: "SSN" → "Social Security Number"</span></li>
</ul>
</div>
</section>No Changes
No changes between these refs.
<section class="flex-semantic-diff">
<p class="flex-semantic-diff__empty">No changes between these refs.</p>
</section>With Additions
Personal Information
- ADDEDAdded field: Middle name
- ADDEDAdded field: Suffix
<section class="flex-semantic-diff">
<div class="flex-semantic-diff__group" data-group="personal-info">
<h3 class="flex-semantic-diff__heading">Personal Information</h3>
<ul class="flex-semantic-diff__list">
<li class="flex-semantic-diff__change" data-category="added"><span class="flex-semantic-diff__badge">ADDED</span><span class="flex-semantic-diff__description">Added field: Middle name</span></li>
<li class="flex-semantic-diff__change" data-category="added"><span class="flex-semantic-diff__badge">ADDED</span><span class="flex-semantic-diff__description">Added field: Suffix</span></li>
</ul>
</div>
</section>With Removals
Contact Details
- REMOVEDRemoved field: Fax number
<section class="flex-semantic-diff">
<div class="flex-semantic-diff__group" data-group="contact">
<h3 class="flex-semantic-diff__heading">Contact Details</h3>
<ul class="flex-semantic-diff__list">
<li class="flex-semantic-diff__change" data-category="removed"><span class="flex-semantic-diff__badge">REMOVED</span><span class="flex-semantic-diff__description">Removed field: Fax number</span></li>
</ul>
</div>
</section>Contract
Documented variants
- NoChanges — Empty state: no changes between refs; renders the "No changes" empty message.
- WithAdditions — One group with multiple added-category changes, demonstrating the "ADDED" badge.
- WithRemovals — One group with a single removed-category change, demonstrating the "REMOVED" badge.
- MixedChanges — Multiple groups with all change categories (added, modified, removed, renamed) showing the full badge palette.
Behavior promises
- ○ Changes are grouped by groupKey; each group renders a heading followed by its list of changes
- ○ Each change item shows the correct category badge label (ADDED, REMOVED, MODIFIED, MOVED, RENAMED)
Source CSS
/* flex-semantic-diff — grouped list of spec changes with per-category
color accents. Callers pass a list of SpecChange records. Presentation
is stateless. */
.flex-semantic-diff {
display: flex;
flex-direction: column;
gap: var(--flex-space-lg);
}
.flex-semantic-diff__group {
display: flex;
flex-direction: column;
gap: var(--flex-space-sm);
}
.flex-semantic-diff__heading {
margin: 0;
color: var(--flex-color-text-muted);
font-size: var(--flex-text-xs);
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.flex-semantic-diff__list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: var(--flex-space-xs);
}
.flex-semantic-diff__change {
display: flex;
align-items: flex-start;
gap: var(--flex-space-sm);
padding: var(--flex-space-sm) var(--flex-space-md);
border: 1px solid var(--flex-color-border);
border-inline-start: 4px solid var(--flex-color-border);
border-radius: var(--flex-radius-sm);
background: var(--flex-color-surface);
}
.flex-semantic-diff__badge {
flex-shrink: 0;
padding: 0 var(--flex-space-xs);
border-radius: var(--flex-radius-sm);
background: var(--flex-color-border);
color: var(--flex-color-text);
font-family: var(--flex-font-mono);
font-size: var(--flex-text-xs);
font-weight: 600;
letter-spacing: 0.04em;
line-height: 1.6;
text-transform: uppercase;
}
.flex-semantic-diff__description {
color: var(--flex-color-text);
font-size: var(--flex-text-sm);
line-height: 1.4;
}
.flex-semantic-diff__change[data-category="added"] {
border-inline-start-color: var(--flex-color-success);
}
.flex-semantic-diff__change[data-category="added"] .flex-semantic-diff__badge {
background: var(--flex-color-success-lighter);
color: var(--flex-color-text);
}
.flex-semantic-diff__change[data-category="removed"] {
border-inline-start-color: var(--flex-color-error);
}
.flex-semantic-diff__change[data-category="removed"] .flex-semantic-diff__badge {
background: var(--flex-color-error-lighter);
color: var(--flex-color-text);
}
.flex-semantic-diff__change[data-category="modified"] {
border-inline-start-color: var(--flex-color-warning);
}
.flex-semantic-diff__change[data-category="modified"] .flex-semantic-diff__badge {
background: var(--flex-color-warning-lighter);
color: var(--flex-color-text);
}
.flex-semantic-diff__change[data-category="moved"] {
border-inline-start-color: var(--flex-color-accent);
}
.flex-semantic-diff__change[data-category="moved"] .flex-semantic-diff__badge {
background: var(--flex-color-info-lighter);
color: var(--flex-color-text);
}
.flex-semantic-diff__change[data-category="renamed"] {
border-inline-start-color: var(--flex-color-info);
}
.flex-semantic-diff__change[data-category="renamed"] .flex-semantic-diff__badge {
background: var(--flex-color-info-lighter);
color: var(--flex-color-text);
}
.flex-semantic-diff__empty {
margin: 0;
color: var(--flex-color-text-muted);
font-size: var(--flex-text-sm);
}
A digital services project by Flexion