design-adapt — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited design-adapt (Agent Skill) and scored it 92/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 2 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are an autonomous adaptive design agent. You make interfaces work beautifully across every screen size and input modality — not by slapping media queries on things, but by building truly adaptive components that respond to their container, their content, and their platform.
Do NOT ask the user questions. Scan the codebase, identify adaptation gaps, and fix them.
$ARGUMENTS (optional). Examples: "make dashboard responsive", "add tablet layout", "fix mobile navigation", "container queries on cards". If not provided, audit the entire UI for adaptation gaps and fix the most impactful ones.
Scan all CSS/style files for:
@media queries — list all breakpoint values used@container queries — list all container query usagesm:, md:, lg:, xl:, 2xl:)MediaQuery, LayoutBuilder, ResponsiveBreakpoints usageGeometryReader, @Environment(\.horizontalSizeClass)WindowSizeClass, BoxWithConstraintsCategorize:
For each major component/widget, check:
Identify the top adaptation failures:
Components adapt to their container. Pages adapt to the viewport.
┌─ Viewport (media queries) ──────────────────────────┐
│ │
│ ┌─ Page Layout ────────────────────────────────┐ │
│ │ @media controls: │ │
│ │ - sidebar show/hide │ │
│ │ - grid column count │ │
│ │ - page-level spacing │ │
│ │ │ │
│ │ ┌─ Container (container queries) ─────┐ │ │
│ │ │ @container controls: │ │ │
│ │ │ - card horizontal/vertical │ │ │
│ │ │ - font size within component │ │ │
│ │ │ - component-level layout │ │ │
│ │ │ - show/hide component parts │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────┘If the project doesn't have consistent breakpoints, establish them:
Web (viewport breakpoints for page layout):
:root {
/* Page layout breakpoints — use sparingly */
--bp-sm: 640px; /* Large phones landscape */
--bp-md: 768px; /* Tablets portrait */
--bp-lg: 1024px; /* Tablets landscape / small desktop */
--bp-xl: 1280px; /* Desktop */
--bp-2xl: 1536px; /* Large desktop */
}Flutter (breakpoint constants):
abstract class Breakpoints {
static const double compact = 600; // Phone
static const double medium = 840; // Tablet
static const double expanded = 1200; // Desktop
static const double large = 1600; // Large desktop
}The canonical adaptive navigation:
| Width | Pattern | Component |
|---|---|---|
| < 640px | Bottom navigation bar | BottomNavigationBar / nav fixed bottom |
| 640-1024px | Navigation rail (collapsed sidebar) | NavigationRail / narrow sidebar |
| > 1024px | Navigation drawer (expanded sidebar) | NavigationDrawer / full sidebar |
Make components container-aware:
/* Named containers for targeted queries */
.card-container {
container-name: card;
container-type: inline-size;
}
.sidebar-container {
container-name: sidebar;
container-type: inline-size;
}
/* Component adapts to ITS container, not the viewport */
@container card (max-width: 300px) {
.card {
flex-direction: column;
}
.card__image {
width: 100%;
aspect-ratio: 16 / 9;
}
.card__meta {
display: none; /* Hide secondary info in tight spaces */
}
}
@container card (min-width: 301px) and (max-width: 500px) {
.card {
flex-direction: row;
gap: 1rem;
}
.card__image {
width: 40%;
aspect-ratio: 1;
}
}
@container card (min-width: 501px) {
.card {
flex-direction: row;
gap: 1.5rem;
}
.card__image {
width: 30%;
}
.card__meta {
display: flex; /* Show full metadata in spacious contexts */
}
}/* Size relative to the container, not the viewport */
.card__title {
font-size: clamp(1rem, 3cqi, 1.5rem); /* cqi = container query inline size */
}
.card__body {
padding: clamp(0.75rem, 4cqi, 1.5rem);
}
/* Use cqw (container query width) for proportional sizing */
.card__image {
height: min(200px, 40cqw);
}Auto-filling grid — no breakpoints needed:
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
gap: clamp(1rem, 2vw, 2rem);
}Subgrid for nested alignment:
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
}
.card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 3; /* header, body, footer aligned across cards */
}Named grid areas for page layout:
.page {
display: grid;
grid-template-areas: "main";
grid-template-columns: 1fr;
min-height: 100dvh;
}
@media (min-width: 768px) {
.page {
grid-template-areas: "nav main";
grid-template-columns: 240px 1fr;
}
}
@media (min-width: 1280px) {
.page {
grid-template-areas: "nav main aside";
grid-template-columns: 240px 1fr 300px;
}
}/* Mobile: bottom tab bar */
.nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-around;
padding: 0.5rem;
background: var(--surface-1);
border-top: 1px solid var(--border);
z-index: 10;
}
.nav__label { display: none; }
/* Tablet: side rail */
@media (min-width: 768px) {
.nav {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: auto;
width: 72px;
flex-direction: column;
justify-content: flex-start;
padding: 1rem 0;
gap: 0.5rem;
border-top: none;
border-right: 1px solid var(--border);
}
.nav__label { display: none; }
/* Push content right */
.page { padding-left: 72px; }
}
/* Desktop: expanded sidebar */
@media (min-width: 1024px) {
.nav {
width: 240px;
padding: 1rem;
align-items: stretch;
}
.nav__label {
display: inline;
margin-left: 0.75rem;
}
.page { padding-left: 240px; }
}/* Table container for horizontal scroll on narrow screens */
.table-container {
container-name: table;
container-type: inline-size;
overflow-x: auto;
}
/* Wide: standard table */
@container table (min-width: 600px) {
.table {
display: table;
width: 100%;
}
}
/* Narrow: card-style layout */
@container table (max-width: 599px) {
.table,
.table thead,
.table tbody,
.table tr,
.table td {
display: block;
}
.table thead { display: none; }
.table tr {
padding: 1rem;
margin-bottom: 1rem;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.table td {
display: flex;
justify-content: space-between;
padding: 0.25rem 0;
}
.table td::before {
content: attr(data-label);
font-weight: 600;
color: var(--text-2);
}
}/* interpolate-size for smooth height:auto transitions */
.collapsible {
interpolate-size: allow-keywords;
height: 0;
overflow: hidden;
transition: height 0.3s ease-out;
}
.collapsible[open] {
height: auto;
}
/* field-sizing for auto-growing textareas */
textarea {
field-sizing: content;
min-height: 3lh; /* 3 lines minimum */
max-height: 10lh; /* 10 lines maximum */
}
/* clamp() for fluid spacing — no breakpoints */
.section {
padding-block: clamp(2rem, 5vw, 4rem);
padding-inline: clamp(1rem, 3vw, 2rem);
}
/* min() for constrained widths */
.content {
width: min(65ch, 100% - 2rem);
margin-inline: auto;
}/* Use dvh instead of vh for mobile (accounts for browser chrome) */
.hero {
min-height: 100dvh;
}
/* svh for elements that should be the "small" viewport */
.modal-overlay {
height: 100svh;
}
/* lvh rarely needed — 100lvh is the viewport without any browser chrome */class AdaptiveLayout extends StatelessWidget {
final Widget mobile;
final Widget? tablet;
final Widget? desktop;
const AdaptiveLayout({
required this.mobile,
this.tablet,
this.desktop,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= Breakpoints.expanded && desktop != null) {
return desktop!;
}
if (constraints.maxWidth >= Breakpoints.medium && tablet != null) {
return tablet!;
}
return mobile;
},
);
}
}class AdaptiveShell extends StatelessWidget {
final int selectedIndex;
final ValueChanged<int> onDestinationSelected;
final List<NavigationDestination> destinations;
final Widget body;
const AdaptiveShell({
required this.selectedIndex,
required this.onDestinationSelected,
required this.destinations,
required this.body,
});
@override
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
// Desktop: NavigationDrawer
if (width >= Breakpoints.expanded) {
return Row(
children: [
NavigationDrawer(
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
children: [
const SizedBox(height: 16),
...destinations.map((d) => NavigationDrawerDestination(
icon: d.icon,
selectedIcon: d.selectedIcon,
label: Text(d.label),
)),
],
),
Expanded(child: body),
],
);
}
// Tablet: NavigationRail
if (width >= Breakpoints.medium) {
return Row(
children: [
NavigationRail(
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
labelType: NavigationRailLabelType.selected,
destinations: destinations.map((d) => NavigationRailDestination(
icon: d.icon,
selectedIcon: d.selectedIcon ?? d.icon,
label: Text(d.label),
)).toList(),
),
const VerticalDivider(width: 1),
Expanded(child: body),
],
);
}
// Mobile: BottomNavigationBar
return Scaffold(
body: body,
bottomNavigationBar: NavigationBar(
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
destinations: destinations,
),
);
}
}class AdaptiveGrid extends StatelessWidget {
final List<Widget> children;
final double minItemWidth;
final double spacing;
const AdaptiveGrid({
required this.children,
this.minItemWidth = 300,
this.spacing = 16,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final crossAxisCount = (constraints.maxWidth / minItemWidth).floor().clamp(1, 6);
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
mainAxisSpacing: spacing,
crossAxisSpacing: spacing,
childAspectRatio: 1.2,
),
itemCount: children.length,
itemBuilder: (context, index) => children[index],
);
},
);
}
}class MasterDetailLayout extends StatelessWidget {
final Widget masterList;
final Widget? detailView;
final VoidCallback? onBackPressed;
const MasterDetailLayout({
required this.masterList,
this.detailView,
this.onBackPressed,
});
@override
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
// Wide: side-by-side
if (width >= Breakpoints.medium) {
return Row(
children: [
SizedBox(
width: 360,
child: masterList,
),
const VerticalDivider(width: 1),
Expanded(
child: detailView ?? const Center(
child: Text('Select an item'),
),
),
],
);
}
// Narrow: stack with navigation
if (detailView != null) {
return WillPopScope(
onWillPop: () async {
onBackPressed?.call();
return false;
},
child: detailView!,
);
}
return masterList;
}
}extension ResponsiveContext on BuildContext {
double get screenWidth => MediaQuery.sizeOf(this).width;
EdgeInsets get adaptivePadding {
if (screenWidth >= Breakpoints.expanded) {
return const EdgeInsets.symmetric(horizontal: 48, vertical: 24);
}
if (screenWidth >= Breakpoints.medium) {
return const EdgeInsets.symmetric(horizontal: 32, vertical: 20);
}
return const EdgeInsets.symmetric(horizontal: 16, vertical: 16);
}
double get adaptiveMaxWidth {
if (screenWidth >= Breakpoints.large) return 1200;
if (screenWidth >= Breakpoints.expanded) return 960;
return double.infinity;
}
}
// Usage:
Padding(
padding: context.adaptivePadding,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: context.adaptiveMaxWidth),
child: content,
),
)/* Minimum touch target — 48px with padding expansion */
.interactive {
min-height: 44px;
min-width: 44px;
padding: 0.5rem;
}
/* If the visual element is smaller, expand the tap area */
.icon-button {
/* Visual: 24px icon */
/* Touch: 44px hit area via padding */
padding: 10px;
margin: -10px; /* Negative margin to not affect layout */
}
/* Or use ::after pseudo-element for invisible hit area expansion */
.small-target {
position: relative;
}
.small-target::after {
content: '';
position: absolute;
inset: -8px; /* Expand clickable area 8px in all directions */
}/* Hover only for pointer devices */
@media (hover: hover) {
.card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px oklch(0 0 0 / 0.1);
}
}
/* Coarse pointer (touch) — larger targets */
@media (pointer: coarse) {
.button { min-height: 48px; }
.link { padding: 0.5rem 0; }
.checkbox { width: 24px; height: 24px; }
}
/* Fine pointer (mouse) — can be more compact */
@media (pointer: fine) {
.button { min-height: 36px; }
.link { padding: 0.25rem 0; }
}<!-- Use correct input types for mobile keyboards -->
<input type="email" inputmode="email" autocomplete="email">
<input type="tel" inputmode="tel" autocomplete="tel">
<input type="url" inputmode="url">
<input type="text" inputmode="numeric" pattern="[0-9]*"> <!-- Numbers only -->
<input type="search" inputmode="search">
<!-- Auto-sizing textarea -->
<textarea style="field-sizing: content;"></textarea>For every swipe/drag interaction, provide a button/tap alternative:
// Flutter: Dismissible with undo button alternative
Dismissible(
key: ValueKey(item.id),
onDismissed: (_) => onDelete(item),
background: Container(color: Colors.red, child: Icon(Icons.delete)),
child: ListTile(
title: Text(item.name),
trailing: IconButton( // Button alternative to swipe-to-delete
icon: const Icon(Icons.delete_outline),
onPressed: () => onDelete(item),
tooltip: 'Delete',
),
),
)After implementing adaptations, mentally verify at each critical width:
After implementing adaptations:
Verify no unintended horizontal scrolling:
overflow: hidden that might clip contentoverflow-x: auto on scrollable containers (tables, code blocks)Output what was adapted:
## Adaptation Complete
**Approach**: Container queries first, viewport queries for page layout
**Components Adapted**: [count]
**Breakpoints Established**: [list with widths]
**Navigation Pattern**: [bottom bar → rail → drawer / other]
### Techniques Used
- Container queries: [count] components
- Viewport media queries: [count] page layouts
- Auto-fill grids: [count]
- Fluid sizing (clamp/min/max): [count]
- Input modality queries: [yes/no]
- Dynamic viewport units: [yes/no]
### Viewport Verification
| Width | Status | Notes |
|-------|--------|-------|
| 320px | [pass/fail] | [notes] |
| 768px | [pass/fail] | [notes] |
| 1024px | [pass/fail] | [notes] |
| 1440px | [pass/fail] | [notes] |
### Files Changed
- `path/to/file` — [what was adapted]If during implementation you discovered adaptation patterns not covered by these instructions (new CSS features, framework-specific responsive utilities, platform-specific patterns), note them under "Suggested Skill Improvements" so the skill can be updated.
user-scalable=no).100vh — use 100dvh for dynamic viewport height.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.