I Put Claude 3.7 to the Test—It Created a UI Pattern I’ve Never Seen Before
What happens when AI challenges conventional design system patterns?
Over the past month, I've been exploring how AI can accelerate the code mockup process for design system components. We started with a structured methodology, then moved into comparing leading AI models like ChatGPT o3, Claude 3.5 Sonnet, and DeepSeek R1.
Today, Anthropic released Claude Code and Claude 3.7 Sonnet, a new model with an extended reasoning mode to mirror the complex chain-of-thought that precedes output by ChatGPT o3-mini-high and DeepSeek R1.
DeepSeek shattered my expectations by demonstrating that AI doesn’t necessarily always get worse as complexity increases, though Claude 3.5 Sonnet proved an impressively close runner-up. Can Claude 3.7 Sonnet’s reasoning one-up DeepSeek’s?
Let’s find out!
Testing The Same Components
To maintain consistency with my previous tests, I evaluated Claude 3.7 Sonnet against the same three components:
<Kbd /> - Our atomic component representing keyboard keys
<Breadcrumbs /> - Our molecular component for navigational breadcrumbs
<Table /> - Our complex organism component for data display
Let's dive in and see how Claude 3.7 Sonnet performed in each case.
<Kbd />
✅ Passable Rough Draft
Pattern: Standalone Component
Approach #1: Individual Key Components
// Single key
<Kbd>A</Kbd>
// Key combination (manual arrangement)
<span>
<Kbd>Ctrl</Kbd>
<span>+</span>
<Kbd>C</Kbd>
</span>
// With styling variants
<Kbd size="small" variant="outline">Tab</Kbd>
// Using special key constants
<Kbd>{KEY_SYMBOLS.COMMAND}</Kbd>
Approach #2: Combination Support Built-in
// Single key
<Kbd>A</Kbd>
// Key combination as array
<Kbd keys={["Ctrl", "C"]} />
// Custom separator
<Kbd keys={["Cmd", "Option", "Esc"]} separator="+" />
// With styling variants
<Kbd keys={["Shift", "Tab"]} size="small" variant="outline" />
// Using key binding object
<Kbd keyBinding={{
modifier: ["Ctrl", "Alt"],
key: "Delete"
}} />
Discrete Props:
Kbd:
keys
,keyBinding
,separator
,size
,variant
Ancillary Resources:
Functions:
normalizeKeyNames()
,formatKeyBinding()
Types:
KeyBinding
,KbdProps
,KbdSize
,KbdVariant
Constants:
KBD_SIZE
,KBD_VARIANT
,KEY_SYMBOLS
, PLATFORM_KEYS
Review
The last test for this component was the first time Claude really "wow!"’d me. Unlike ChatGPT o3-mini-high and DeepSeek R1, which considered but rejected a compound component approach, Claude’s lack of explicit reasoning actually led to a more exploratory result—one that felt closer to something that might actually be submitted to VSDS at VividSeats.
That said, Claude 3.7 also skipped the compound component approach, allowing some logical flaws to slip through.
Approach #1 is simple and straightforward, but the way spans are used makes it unclear what the actual implementation would accomplish without variants.
Approach #2 is cleaner, and while I initially questioned whether it was distinct enough, the switch from text child to a
keys
prop makes it a valid alternative.However, the difference between
key
andkeyBinding
is unclear beyond passingkeyBinding
toformatKeyBinding()
. The extra structure for labelling modifiers also feels unnecessary.
Even if modifiers are needed labels, I’d avoid passing objects through props when possible—key/value pairs usually should just be explicit props instead:
<Kbd modifier={['Ctrl', 'Alt']} key="Delete" />
The best parts of this result are:
✅ Cross-platform considerations
✅ Well-aligned ancillary resources
✅ Better code quality (const assertions!?)
It would be even better if KbdSize
and KbdVariant
were inferred from the KBD_SIZE
and KBD_VARIANT
constants via typeof
, but that’s something for review.
Overall, some details in the usage snippets are a bit dubious, but the output is solid enough to present. Now, let’s see what happens when we give Claude 3.7 more to work with!
<Breadcrumbs />
⚠️ Questionable
Pattern: Compound component
Approach #1: Compound Component with Explicit Items
Subcomponents:
Breadcrumbs.Item
<Breadcrumbs testId="site-breadcrumbs">
<Breadcrumbs.Item href="/">Home</Breadcrumbs.Item>
<Breadcrumbs.Item href="/products">Products</Breadcrumbs.Item>
<Breadcrumbs.Item href="/products/electronics">Electronics</Breadcrumbs.Item>
<Breadcrumbs.Item active>Smartphones</Breadcrumbs.Item>
</Breadcrumbs>
Approach #2: Compound Component with Custom Separator
Subcomponents:
Breadcrumbs.Item
,Breadcrumbs.Separator
<Breadcrumbs
testId="custom-breadcrumbs"
separator={<Breadcrumbs.Separator>/</Breadcrumbs.Separator>}
>
<Breadcrumbs.Item href="/">Home</Breadcrumbs.Item>
<Breadcrumbs.Item href="/products">Products</Breadcrumbs.Item>
<Breadcrumbs.Item active>Categories</Breadcrumbs.Item>
</Breadcrumbs>
Approach #3: Combination Support Built-in
const items: BreadcrumbItem[] = [
{ label: 'Home', href: '/' },
{ label: 'Products', href: '/products' },
{ label: 'Electronics', href: '/products/electronics' },
{ label: 'Smartphones', active: true }
];
<Breadcrumbs
testId="data-driven-breadcrumbs"
items={items}
/>
Discrete Props:
Breadcrumbs:
maxDisplayed
,items
(Approach #3),separator
,truncate
,onCollapse()
,onExpand()
Breadcrumbs.Item:
active
,href
,icon
,onPress()
Ancillary Resources:
Hooks:
useBreadcrumbsContext()
,useCollapsedItems()
Functions:
formatBreadcrumbs()
Types:
BreadcrumbItem
,BreadcrumbsContextValue
Enums:
BreadcrumbDisplayMode
Constants:
DEFAULT_SEPARATOR
, DEFAULT_MAX_DISPLAYED
Review
Approach #1 and Approach #3 are both passable, Approach #2 is not since it’s really just the same as Approach #1 but with an extra subcomponent. Unfortunately, this is enough of a loss of credibility to keep this result in the Questionable category, though it’s presentable if we just remove Approach #2.
Again, the code snippets are much better than Claude 3.5. The hooks make more sense and they align with other resources (e.g. useBreadcrumbsContext()
and BreadcrumbsContextValue
.) Interestingly, Claude 3.7 also described the return type of useCollapsedItems()
, which is an object that includes some state and a method toggleCollapse()
.
For anyone curious about what BreadcrumbDisplayMode
is, here you go!
enum BreadcrumbDisplayMode {
All = "all",
Collapsed = "collapsed",
FirstLast = "firstLast"
}
<Table />
✅ Passable Rough Draft
Pattern: Compound component
Variants:
Subcomponents:
Approach #1: Full Compound Structure with Context
// Usage example
const BasicTable = () => (
<Table testId="user-data-table">
<Table.Caption>List of active users</Table.Caption>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>Email</Table.HeaderCell>
<Table.HeaderCell>Role</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{users.map((user) => (
<Table.Row key={user.id}>
<Table.Cell>{user.name}</Table.Cell>
<Table.Cell>{user.email}</Table.Cell>
<Table.Cell>{user.role}</Table.Cell>
</Table.Row>
))}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.Cell colSpan={3}>Total Users: {users.length}</Table.Cell>
</Table.Row>
</Table.Footer>
</Table>
);
// Sortable example
const SortableTable = () => {
const [sortConfig, setSortConfig] = useState<SortConfig>({ key: 'name', direction: 'ascending' });
const handleSort = (key: string) => {
// Sort logic here
};
return (
<Table testId="sortable-user-table">
<Table.Header>
<Table.Row>
<Table.HeaderCell sortable onSort={() => handleSort('name')} sortDirection={sortConfig.key === 'name' ? sortConfig.direction : undefined}>
Name
</Table.HeaderCell>
<Table.HeaderCell sortable onSort={() => handleSort('email')} sortDirection={sortConfig.key === 'email' ? sortConfig.direction : undefined}>
Email
</Table.HeaderCell>
<Table.HeaderCell>Role</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{/* Sorted users */}
</Table.Body>
</Table>
);
};
Approach #2: Compositional with Column Definition
// Usage example
const ColumnDefinedTable = () => {
const columns: Array<ColumnDefinition> = [
{ key: 'name', header: 'Name', sortable: true },
{ key: 'email', header: 'Email', sortable: true },
{ key: 'role', header: 'Role' }
];
return (
<Table
testId="user-table-defined"
columns={columns}
data={users}
caption="List of active users"
onSort={(key, direction) => { /* Handle sort */ }}
>
<Table.Pagination
totalItems={users.length}
itemsPerPage={10}
onPageChange={(page) => { /* Handle page change */ }}
/>
</Table>
);
};
// With custom cell rendering
const CustomCellTable = () => {
const columns: Array<ColumnDefinition> = [
{
key: 'name',
header: 'Name',
render: (user) => <strong>{user.name}</strong>
},
{
key: 'status',
header: 'Status',
render: (user) => (
<Table.StatusCell status={user.status} />
)
},
{
key: 'actions',
header: 'Actions',
render: (user) => (
<Table.ActionCell>
<Button onPress={() => editUser(user)}>Edit</Button>
<Button onPress={() => deleteUser(user)}>Delete</Button>
</Table.ActionCell>
)
}
];
return (
<Table
testId="custom-cell-table"
columns={columns}
data={users}
/>
);
};
Discrete Props:
Table:
columns
,data
(Approach #2),striped
,bordered
,compact
,hoverable
,sortConfig
,onSort()
Table.Row:
selected
,onPress()
Table.Cell:
colSpan
,rowSpan
,align
,truncate
Table.HeaderCell:
colSpan
,rowSpan
,align
,sortable
,sortDirection
,onSort()
Table.Pagination:
currentPage
,totalItems
,itemsPerPage
,onPageChange()
Ancillary Resources:
Hooks:
useSortableTable()
,useSelectableTable()
,usePaginatedTable()
,useTable()
Functions:
sortTableData()
,filterTableData()
,paginateTableData()
Types:
ColumnDefinition
,SortConfig
,SortableTableResult
,SelectableTableOptions
,SelectableTableResult
,PaginationOptions
,PaginatedTableResult
,TableContext
Enums:
SortDirection
,TableCellAlignment
Constants:
DEFAULT_ITEMS_PER_PAGE
,DEFAULT_PAGE_SIZES
,DEFAULT_SORT_DIRECTION
Review
At first glance, both approaches feel over-engineered, and I wasn’t sure Approach #2 was distinct enough to be a true alternative. But looking closer, I noticed something I hadn’t seen before.
Compound components are only mutually exclusive to standalone components—they’re often used as base components both inside and outside the design system. When this happens within the design system, we call it a Compound-as-Base Hybrid.
Typically, compound components take subcomponents as children, but some cases invert this relationship, allowing a subcomponent (often a Group
) to take the parent as children instead.
A New Pattern Emerges
Approach #2 does something interesting with Table.ActionCell
and Table.StatusCell
. Instead of using Table.Cell
as just another subcomponent, Claude 3.7 treats it as a base subcomponent—essentially creating a base pattern within a compound component.
I’ve seen entire compound components used as base components, but I’ve never come across a compound component with a base subcomponent like this.
Potential Controversy
Is this a novel pattern or just so rare that I haven't encountered it before? Hard to say. Typically, base component variants are more generic—for example, atomic components like Button
use system colors (error
, info
, success
, warning
, etc.) to compose specialized variants like InfoButton
, SuccessButton
, etc.
For more complex components, specialized variants are usually built outside the design system, composing agnostic design system components into business-specific variants—think a Card
component spawning NewsCard
, BlogCard
, and OfferCard
.
The biggest mistake teams make when building design systems is overfitting components to their specific business problems. And while it’s okay to tailor implementation details to company needs, a design system’s core purpose is to solve common, business-agnostic UI challenges.
A good design system should be flexible enough that if your org pivoted to a new industry, you could rebuild your platform using the same components. If your design system is entangled with business logic, you’re doing it wrong.
Applying This Pattern
This is where this new pattern could be controversial. For atomic components, specialization typically aligns with business-agnostic concerns like intent and system colors. But are Table.ActionCell
and Table.StatusCell
really business-agnostic enough to live in the design system?
Some might argue this makes the pattern an anti-pattern, but I see potential use cases—particularly in something like a Form
component, where Form.Input
could act as a base for specialized variants like Form.NumericalInput
, Form.ColorInput
, Form.PhoneInput
, Form.EmailInput
, etc.
Bringing this to the VSDS team would spark quite a debate—a roundtable discussion, if you will. And honestly? We love a good roundtable!