소스 정보
- 저장소
- miles990/claude-software-skills
- 최근 소스 활동
- 2026년 1월 8일 02:34
- 감지된 SKILL.md 언어
- 영어
- 스타
- 20
- 포크
- 5
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
SOC 직업 분류 기준
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/miles990/claude-software-skills --skill ux-principles명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SKILL.md 표시 중
Enterprise-grade repository analysis with arc42/C4 architecture documentation, technical debt quantification, security assessment, and multi-stakeholder reporting
Claude Code Plugin 開發、發布、安裝、更新與 Marketplace 管理完整指南
Flame Engine core fundamentals - components, input, collision, camera, animation, scenes
| name | ux-principles |
| description | User experience design principles for developers |
| domain | software-design |
| version | 1.0.0 |
| tags | ["ux","usability","accessibility","responsive","design-system","wcag"] |
| triggers | {"keywords":{"primary":["ux","user experience","usability","accessibility","a11y","wcag"],"secondary":["responsive","design system","heuristics","user flow","information architecture"]},"context_boost":["frontend","ui","design","user","interface"],"context_penalty":["backend","database","devops"],"priority":"medium"} |
Essential UX principles that every developer should know. Good UX isn't just design—it's built into code, architecture, and technical decisions.
// ❌ No feedback
async function saveDocument() {
await api.save(document);
}
// ✅ Clear feedback
async function saveDocument() {
setStatus('saving');
try {
await api.save(document);
setStatus('saved');
showToast('Document saved');
} catch (error) {
setStatus('error');
showToast('Failed to save. Please try again.');
}
}
<!-- Progress indicators -->
<button disabled={isLoading}>
{isLoading ? (
<>
<Spinner /> Saving...
</>
) : (
'Save'
)}
</button>
<!-- Upload progress -->
<progress value={uploadProgress} max="100" />
<span>{uploadProgress}% uploaded</span>
// ❌ Technical jargon
"Error: ECONNREFUSED 127.0.0.1:5432"
// ✅ Human language
"We couldn't connect to the database. Please check your internet connection."
// ❌ Developer terms
"Record not found in users table"
// ✅ User terms
"We couldn't find an account with that email address"
// Undo functionality
function deleteItem(id: string) {
const item = items.find(i => i.id === id);
setItems(items.filter(i => i.id !== id));
showToast({
message: 'Item deleted',
action: {
label: 'Undo',
onClick: () => setItems([...items, item])
},
duration: 5000
});
}
// Cancel long operations
const controller = new AbortController();
async function uploadFile(file: File) {
try {
await fetch('/upload', {
method: 'POST',
body: file,
signal: controller.signal
});
} catch (e) {
if (e.name === 'AbortError') {
showToast('Upload cancelled');
}
}
}
<button onClick={ controller.()}> </button>
// Design tokens for consistency
const theme = {
colors: {
primary: '#007bff',
danger: '#dc3545',
success: '#28a745',
},
spacing: {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
},
borderRadius: {
sm: '4px',
md: '8px',
lg: '16px',
}
};
// Consistent button patterns
<Button variant="primary">Save</Button> // Main action
<Button variant="secondary">Cancel</Button> // Secondary action
<Button variant="danger">Delete</Button> // Destructive action
// Confirm destructive actions
function deleteAccount() {
const confirmed = await confirm({
title: 'Delete Account?',
message: 'This action cannot be undone. All your data will be permanently deleted.',
confirmText: 'Delete Account',
confirmVariant: 'danger'
});
if (confirmed) {
await api.deleteAccount();
}
}
// Input constraints
<input
type="number"
min={0}
max={100}
step={1}
inputMode="numeric"
/>
// Disable invalid actions
<button
disabled={!isFormValid || isSubmitting}
title={!isFormValid ? 'Please fill all required fields' : undefined}
>
Submit
</button>
<!-- ❌ Div soup -->
<div class="nav">
<div class="nav-item" onclick="navigate()">Home</div>
</div>
<!-- ✅ Semantic HTML -->
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
<!-- ❌ Missing labels -->
<input type="text" placeholder="Email">
<!-- ✅ Proper labeling -->
<label for="email">Email address</label>
<input type="email" id="email" name="email" required>
<!-- Live regions for dynamic content -->
<div aria-live="polite" aria-atomic="true">
{statusMessage}
</div>
<!-- Modal dialogs -->
<div
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
aria-describedby="dialog-description"
>
<h2 id="dialog-title">Confirm Action</h2>
<p id="dialog-description">Are you sure you want to proceed?</p>
</div>
<!-- Loading states -->
<button aria-busy={isLoading} aria-disabled={isLoading}>
{isLoading ? 'Loading...' : 'Submit'}
</button>
<!-- Expandable sections -->
<button
aria-expanded={isOpen}
aria-controls="panel-content"
>
Show Details
</button>
<div id="panel-content" hidden={!isOpen}>
Details here...
// Focus management
function openModal() {
setIsOpen(true);
// Focus first focusable element
setTimeout(() => {
modalRef.current?.querySelector('button, [href], input')?.focus();
}, 0);
}
function closeModal() {
setIsOpen(false);
// Return focus to trigger
triggerRef.current?.focus();
}
// Focus trap in modals
function handleKeyDown(e: KeyboardEvent) {
if (e.key === 'Tab') {
const focusable = modalRef.current?.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable?.[0];
const last = focusable?.[focusable.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last?.focus();
} else if (!e.shiftKey && . === last) {
e.();
first?.();
}
}
(e. === ) {
();
}
}
/* WCAG AA: 4.5:1 for normal text, 3:1 for large text */
:root {
--text-primary: #1a1a1a; /* High contrast on white */
--text-secondary: #6b7280; /* 4.5:1 on white */
--text-on-primary: #ffffff; /* White on brand color */
}
/* Don't rely on color alone */
.error-message {
color: #dc3545;
/* Also include icon */
&::before {
content: "⚠ ";
}
}
/* Focus indicators */
:focus-visible {
outline: 2px solid var(--focus-color);
outline-offset: 2px;
}
/* Never remove focus styles entirely */
/* ❌ */ :focus { outline: none; }
/* Base styles for mobile */
.container {
padding: 16px;
}
.grid {
display: grid;
gap: 16px;
grid-template-columns: 1fr;
}
/* Tablet and up */
@media (min-width: 768px) {
.container {
padding: 24px;
}
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* Desktop */
@media (min-width: 1024px) {
.container {
padding: 32px;
max-width: 1200px;
margin: 0 auto;
}
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* Minimum 44x44px touch targets (WCAG) */
.button {
min-height: 44px;
min-width: 44px;
padding: 12px 16px;
}
/* Adequate spacing between interactive elements */
.button-group {
display: flex;
gap: 8px;
}
/* Make entire area tappable */
.card-link {
position: relative;
}
.card-link::after {
content: '';
position: absolute;
inset: 0;
}
// Optimistic updates
function likePost(postId: string) {
// Update UI immediately
setLiked(true);
setLikeCount(prev => prev + 1);
// Sync with server in background
api.likePost(postId).catch(() => {
// Rollback on failure
setLiked(false);
setLikeCount(prev => prev - 1);
showToast('Failed to like post');
});
}
// Skeleton loading
function PostList() {
if (isLoading) {
return (
<div className="post-list">
{[1, 2, 3].map(i => (
<div key={i} className="post-skeleton">
<div className="skeleton-avatar" />
<div className="skeleton-text" />
<div className= />
))}
);
}
;
}
<!-- Critical content first -->
<head>
<!-- Preload critical assets -->
<link rel="preload" href="/fonts/main.woff2" as="font" crossorigin>
<link rel="preload" href="/hero-image.webp" as="image">
<!-- Defer non-critical CSS -->
<link rel="preload" href="/non-critical.css" as="style" onload="this.rel='stylesheet'">
</head>
<!-- Lazy load below-fold images -->
<img src="product.jpg" loading="lazy" alt="Product image">
<!-- Intersection Observer for infinite scroll -->
<div ref={sentinelRef}>
{hasMore && <Spinner />}
</div>
<!-- Clear labels and help text -->
<div class="form-field">
<label for="password">Password</label>
<input
type="password"
id="password"
aria-describedby="password-help"
minlength="8"
>
<small id="password-help">At least 8 characters</small>
</div>
<!-- Inline validation -->
<input
type="email"
class={hasError ? 'input-error' : ''}
aria-invalid={hasError}
aria-describedby={hasError ? 'email-error' : undefined}
>
{hasError && (
<span id="email-error" class="error-message" role="alert">
Please enter a valid email address
</span>
)}
// Auto-save drafts
const debouncedSave = useMemo(
() => debounce((data) => saveDraft(data), 1000),
[]
);
useEffect(() => {
debouncedSave(formData);
}, [formData]);
// Clear error on input
function handleChange(field: string, value: string) {
setFormData(prev => ({ ...prev, [field]: value }));
setErrors(prev => ({ ...prev, [field]: undefined }));
}
// Preserve form state on navigation
useBeforeUnload(
useCallback((e) => {
if (hasUnsavedChanges) {
e.preventDefault();
return 'You have unsaved changes';
}
}, [hasUnsavedChanges])
);
function EmptyState({ type }: { type: 'search' | 'empty' | 'error' }) {
const content = {
search: {
icon: <SearchIcon />,
title: 'No results found',
message: 'Try adjusting your search or filters',
action: <Button onClick={clearFilters}>Clear filters</Button>
},
empty: {
icon: <FolderIcon />,
title: 'No projects yet',
message: 'Create your first project to get started',
action: <Button onClick={createProject}>Create Project</Button>
},
error: {
icon: <AlertIcon />,
title: 'Something went wrong',
message: 'We couldn\'t load the data. Please try again.',
action: <Button onClick=>Retry
}
}[];
(
);
}