Skip to content

Commit 1775906

Browse files
authored
chore(repo): Add cursor rule for clerk-js UI (#6234)
1 parent 713a20d commit 1775906

File tree

2 files changed

+223
-0
lines changed

2 files changed

+223
-0
lines changed

.changeset/curly-facts-end.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

.cursor/rules/clerk-js-ui.mdc

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
---
2+
description: Coding rules for Clerk-JS UI and AIO Components
3+
globs: packages/clerk-js/src/ui/**/*.ts,packages/clerk-js/src/ui/**/*.tsx
4+
alwaysApply: false
5+
---
6+
# Clerk-JS UI Patterns & Architecture
7+
8+
## 1. Element Descriptors System
9+
The UI system uses a powerful element descriptor pattern for styling and customization. Element descriptors are the foundation of Clerk's theming system, allowing developers to customize any component through the `appearance.elements` configuration. Each descriptor generates a unique, stable CSS class that can be targeted for styling, ensuring consistent theming across the entire application.
10+
11+
### How to add new descriptors
12+
- Scan the provided files for places that require a descriptor.
13+
- Element descriptors should be always be camelCase
14+
- First add them as types in `packages/types/src/appearance.ts` then build the package
15+
- After append them to `APPEARANCE_KEYS` in `packages/clerk-js/src/ui/customizables/elementDescriptors.ts`
16+
- Use them in the original files.
17+
- If linting errors occur try to `cd ./packages/types && pnpm build`.
18+
19+
### Basic Usage
20+
```tsx
21+
// Basic component with descriptor
22+
// Use when you need to style a basic component that maps to a single appearance element
23+
<Box elementDescriptor={descriptors.formButtonPrimary} />
24+
// Generated class: "cl-formButtonPrimary"
25+
26+
// Multiple descriptors
27+
// Use when a component needs to inherit styles from multiple appearance elements
28+
// Example: An icon that is both an icon and specifically an initials-based icon
29+
<Box elementDescriptor={[descriptors.icon, descriptors.iconInitials]} />
30+
// Generated classes: "cl-icon cl-iconInitials"
31+
32+
// With element ID
33+
// Use when you need to style a specific instance/variant of a component
34+
// Example: A social button for a specific provider like Google, GitHub, etc.
35+
<Box
36+
elementDescriptor={descriptors.icon}
37+
elementId={descriptors.icon.setId('google')}
38+
/>
39+
// Generated classes: "cl-icon cl-icon__google"
40+
```
41+
42+
### State Classes
43+
Element descriptors automatically handle state classes:
44+
```tsx
45+
<Box
46+
elementDescriptor={descriptors.socialButtonsIconButton}
47+
isLoading={true} // Adds 'cl-loading' class
48+
isActive={true} // Adds 'cl-active' class
49+
hasError={true} // Adds 'cl-error' class
50+
isOpen={true} // Adds 'cl-open' class
51+
/>
52+
```
53+
54+
## 2. Form Handling Pattern
55+
56+
### Form State Management
57+
```tsx
58+
// Form root with context
59+
const FormRoot = (props: FormProps) => {
60+
const card = useCardState();
61+
const status = useLoadingStatus();
62+
const [submittedWithEnter, setSubmittedWithEnter] = useState(false);
63+
64+
return (
65+
<FormState.Provider value={{
66+
isLoading: status.isLoading,
67+
isDisabled: card.isLoading || status.isLoading,
68+
submittedWithEnter
69+
}}>
70+
<FormPrim elementDescriptor={descriptors.form} {...props} />
71+
</FormState.Provider>
72+
);
73+
};
74+
```
75+
76+
### Form Controls
77+
```tsx
78+
// Using form controls with validation and localization
79+
const emailField = useFormControl('email', initialValue, {
80+
type: 'email',
81+
label: localizationKeys('formFieldLabel__email'),
82+
placeholder: localizationKeys('formFieldInputPlaceholder__email'),
83+
buildErrorMessage: errors => createEmailError(errors)
84+
});
85+
86+
// Usage in JSX
87+
<Form.ControlRow elementId={emailField.id}>
88+
<Form.PlainInput {...emailField.props} />
89+
</Form.ControlRow>
90+
```
91+
92+
### Form Submission
93+
```tsx
94+
const handleSubmit = async (e: React.FormEvent) => {
95+
e.preventDefault();
96+
97+
status.setLoading();
98+
card.setLoading();
99+
card.setError(undefined);
100+
101+
try {
102+
await submitData();
103+
onSuccess();
104+
} catch (error) {
105+
handleError(error, [emailField], card.setError);
106+
} finally {
107+
status.setIdle();
108+
card.setIdle();
109+
}
110+
};
111+
```
112+
113+
## 3. Localization Pattern
114+
115+
Attention: Hard coded values are not allowed to be rendered. All values should be localized with the methods provided below.
116+
117+
### Basic Usage
118+
```tsx
119+
// Using the localization hook
120+
const { t, translateError, locale } = useLocalizations();
121+
122+
// Translating a key
123+
const translatedText = t(localizationKeys('formButtonPrimary'));
124+
125+
// Translating with parameters
126+
const translatedWithParams = t(localizationKeys('unstable__errors.passwordComplexity.minimumLength', {
127+
length: minLength
128+
}));
129+
```
130+
131+
### Component Integration
132+
```tsx
133+
// Component with localization
134+
<Button
135+
elementDescriptor={descriptors.formButtonPrimary}
136+
localizationKey={localizationKeys('formButtonPrimary')}
137+
/>
138+
139+
// Error translation
140+
const errorMessage = translateError(apiError);
141+
```
142+
143+
## 4. Styled System Pattern
144+
145+
### Basic Usage
146+
```tsx
147+
// Using the styled system
148+
<Box
149+
sx={theme => ({
150+
backgroundColor: theme.colors.$primary100,
151+
padding: theme.space.$4,
152+
borderRadius: theme.radii.$md
153+
})}
154+
/>
155+
156+
// With responsive values
157+
<Flex
158+
direction={['column', 'row']}
159+
gap={[2, 4, 6]}
160+
/>
161+
```
162+
163+
## 5. Common UI Patterns
164+
165+
### Card Pattern
166+
```tsx
167+
const MyCard = () => (
168+
<Card>
169+
<Card.Root>
170+
<Card.Header>
171+
<Card.Title>Title</Card.Title>
172+
</Card.Header>
173+
<Card.Content>Content</Card.Content>
174+
<Card.Footer>Footer</Card.Footer>
175+
</Card.Root>
176+
</Card>
177+
);
178+
```
179+
180+
### Form Container Pattern
181+
```tsx
182+
const MyForm = () => (
183+
<FormContainer
184+
headerTitle={localizationKeys('page.title')}
185+
headerSubtitle={localizationKeys('page.subtitle')}
186+
>
187+
<Form.Root onSubmit={handleSubmit}>
188+
<Form.ControlRow>
189+
{/* Form fields */}
190+
</Form.ControlRow>
191+
<FormButtons
192+
isDisabled={!isValid}
193+
onReset={handleReset}
194+
/>
195+
</Form.Root>
196+
</FormContainer>
197+
);
198+
```
199+
200+
## 6. Best Practices
201+
202+
1. **State Management**
203+
- Use `useCardState` for card-level state
204+
- Use `useFormState` for form-level state
205+
- Use `useLoadingStatus` for loading states
206+
207+
2. **Error Handling**
208+
- Always use `handleError` utility for API errors
209+
- Provide field states for proper error mapping
210+
- Use `translateError` for localized error messages
211+
212+
3. **Styling**
213+
- Use element descriptors for consistent styling
214+
- Use the styled system for custom styles
215+
- Follow the theme token system
216+
217+
4. **Forms**
218+
- Use `useFormControl` for form field state
219+
- Implement proper validation
220+
- Handle loading and error states
221+
- Use localization keys for labels and placeholders

0 commit comments

Comments
 (0)