>;
} {
return {
root: { 'data-component': component },
element: (name, action) => ({
'data-element': name,
...(action && { 'data-action': action })
})
};
}
// Usage
function ProductCard({ product }: Props) {
const sel = createSelectors('ProductCard');
return (
{/* TypeScript will error if you use 'invalid-element' */}
);
}
```
---
### Vanilla JavaScript / Web Components
```javascript
// product-card.js
class ProductCard extends HTMLElement {
connectedCallback() {
const product = JSON.parse(this.getAttribute('product'))
this.innerHTML = `
${product.name}
$${product.price}
`
this.querySelector('[data-element="add-to-cart"]').addEventListener('click', () =>
this.handleAddToCart(product),
)
}
}
customElements.define('product-card', ProductCard)
```
---
### Server-Side Templates (PHP, Django, Rails, etc.)
```html
{{ $product->name }}
{{ product.name }}
<%= product.name %>
```
---
## Using Stable Selectors in Fullstory
### Searching by Selector
```
# Find all ProductCard components
css selector: [data-component="ProductCard"]
# Find add-to-cart buttons
css selector: [data-element="add-to-cart"]
# Find add-to-cart within ProductCard
css selector: [data-component="ProductCard"] [data-element="add-to-cart"]
```
### Creating Defined Elements
When creating defined elements in Fullstory, use stable selectors:
| Element Name | Selector |
| ------------------ | ---------------------------------------------------------------- |
| Add to Cart Button | `[data-element="add-to-cart"]` |
| Product Card | `[data-component="ProductCard"]` |
| Search Input | `[data-element="search-input"]` |
| Checkout Submit | `[data-component="CheckoutForm"] [data-element="submit-button"]` |
### Combining with Element Properties
Stable selectors and Element Properties work together:
```html
```
| Attribute | Purpose |
| --------------------------- | ------------------------------------ |
| `data-component` | Stable selector for searching |
| `data-element` | Stable selector for specific element |
| `data-fs-element` | Fullstory defined element name |
| `data-fs-properties-schema` | Fullstory element properties schema |
---
## ✅ GOOD Implementation Examples
### Example 1: E-commerce Product Grid
```html
Featured Products
Wireless Headphones
$149.99
$199.99
```
### Example 2: Multi-Step Form
```html
```
### Example 3: Navigation with Dropdowns
```html
```
---
## ❌ BAD Implementation Examples
### Example 1: Generic Names
```html
```
**Why it's bad:** Every component has "image", "text", "button" - searches return everything.
**✅ CORRECTED:**
```html
```
### Example 2: Position-Based Names
```html
First product
Second product
Third product
```
**Why it's bad:** If sort order changes, "item-0" is now a different product.
**✅ CORRECTED:**
```html
First product
Second product
Third product
```
### Example 3: Appearance-Based Names
```html
Navigation
```
**Why it's bad:** If design changes (blue → green, sidebar moves right), names become wrong.
**✅ CORRECTED:**
```html
Navigation
```
---
## Advanced Patterns
### Virtualized Lists / Infinite Scroll
For virtualized content where DOM elements are recycled:
```tsx
// React with react-window or react-virtualized
function VirtualizedProductList({products}) {
return (
{({index, style}) => (
)}
)
}
```
**Key Principle**: Use stable business identifiers (`data-product-id`), not positional indices.
---
### Shadow DOM / Web Components
Shadow DOM encapsulates styles but data attributes still work:
```javascript
class ProductCard extends HTMLElement {
constructor() {
super()
this.attachShadow({mode: 'open'})
}
connectedCallback() {
// Set attributes on the host element (light DOM)
this.setAttribute('data-component', 'ProductCard')
this.setAttribute('data-element', 'card')
// Shadow DOM content also gets attributes
this.shadowRoot.innerHTML = `
`
}
}
// For Fullstory to see shadow DOM content, enable deep capture:
// FS.setProperties({ type: 'page', properties: { shadowDomEnabled: true } });
```
**Fullstory Note**: Contact Fullstory support about Shadow DOM capture configuration for your account.
---
### Micro-Frontends
When multiple teams own different parts of the UI, namespace your selectors:
```html
```
**Namespace Convention**: `{Team}.{Component}` prevents collisions.
---
### A/B Tests and Feature Flags
Track variants for analysis:
```html
```
**In Fullstory**: Search by `[data-experiment="homepage-cta-2024"][data-variant="treatment-green"]` to analyze specific variants.
---
### Dynamic/Lazy-Loaded Content
Ensure selectors are present when content loads:
```tsx
// React with Suspense
function ProductDetails({productId}) {
return (
Loading...
}
>
)
}
function ProductDetailsContent({productId}) {
const product = use(fetchProduct(productId))
return (
{/* content */}
)
}
```
**Note**: The `data-state` attribute helps distinguish loading vs loaded states in Fullstory searches.
---
### Iframes (Cross-Origin Limitations)
For same-origin iframes, selectors work normally. For cross-origin:
```html
```
**Limitation**: Fullstory cannot directly capture cross-origin iframe content. The iframe must have its own Fullstory snippet installed.
---
## Best Practices
### 1. Annotate at Development Time
Add annotations as you write components, not as an afterthought:
```jsx
// ✅ Good habit: Add annotations as you code
function ProductCard({product}) {
return (
)
}
```
### 2. Document Your Conventions
Create a team style guide:
```markdown
## Stable Selector Conventions
### Component Names
- Use PascalCase: `ProductCard`, `CheckoutForm`
- Match your component file/class name
### Element Names
- Use kebab-case: `add-to-cart`, `search-input`
- Describe purpose, not appearance
- Be specific: `product-name` not `name`
### Required Annotations
- All buttons and links
- All form inputs
- All cards in lists
- Modal and dropdown triggers
```
### 3. Combine with Privacy Controls
```html
```
### 4. Use Consistent Depth
Don't over-nest annotations:
```html
```
---
## Troubleshooting
### Selectors Not Working in Fullstory
**Check in browser DevTools:**
1. Inspect the element
2. Verify `data-component` and `data-element` attributes exist
3. Check for typos in attribute names
**Common issues:**
- Framework stripping data attributes in production
- SSR/hydration mismatch
- Conditional rendering removing the element
### Too Many Search Results
**Problem:** Searching `[data-element="button"]` returns hundreds of results
**Solution:** Be more specific:
```
[data-component="ProductCard"] [data-element="add-to-cart"]
```
### Attributes Stripped in Production
Check your build tool configuration:
```javascript
// webpack.config.js - DON'T strip data-* attributes
optimization: {
minimizer: [
new HtmlWebpackPlugin({
minify: {
// Keep data-* attributes
removeDataAttributes: false,
},
}),
]
}
```
---
## KEY TAKEAWAYS FOR AGENT
When helping developers implement stable selectors:
### Core Principles
1. **Framework-agnostic solution**: Works in React, Angular, Vue, Svelte, Next.js, Astro, vanilla JS, server-side templates
2. **Primary attributes**: `data-component` (PascalCase) and `data-element` (kebab-case)
3. **Extended attributes for AI/CUA**: `data-action`, `data-state`, `data-variant`
4. **Name by purpose, not appearance**: "add-to-cart" not "blue-button"
5. **Annotate interactive elements**: Buttons, inputs, links, cards in lists
6. **Combine with Element Properties**: Stable selectors for search, Element Properties for analytics data
7. **Combine with ARIA**: Use both data-_ and aria-_ for maximum AI/accessibility compatibility
8. **No plugins required**: Manual annotation works everywhere
### CUA/AI Agent Considerations
- **`data-action`**: Helps AI understand what interaction will do ("add-item", "submit-form", "toggle-menu")
- **`data-state`**: Helps AI understand current element state ("loading", "disabled", "expanded")
- **ARIA integration**: Ensure `aria-label` provides human-readable context alongside data-\* targeting
- **Consistent naming**: AI agents learn patterns—be consistent across your codebase
### Questions to Ask Developers
1. "What framework are you using?" (React, Vue, Angular, Next.js, Astro, etc.)
2. "Are your class names dynamic?" (CSS Modules, styled-components, Tailwind)
3. "What elements do you need to reliably search for in Fullstory?"
4. "Do you have a component naming convention already?"
5. "Are you using E2E testing tools?" (May want to align with data-testid)
6. "Do you use micro-frontends or multiple teams?" (Need namespace strategy)
7. "Is AI/automation tooling on your roadmap?" (Add extended attributes now)
### Implementation Checklist
```markdown
Phase 1: Core Implementation
□ Identify interactive elements that need tracking
□ Add data-component to component root elements
□ Add data-element to buttons, inputs, links, cards
□ Use specific, purpose-based names (not appearance/position)
□ Test selectors in browser DevTools
□ Verify attributes survive production build
□ Create defined elements in Fullstory using data-\* selectors
Phase 2: AI/CUA Readiness (Recommended)
□ Add data-action to buttons and interactive elements
□ Add data-state for elements with multiple states
□ Ensure ARIA attributes complement data-\* selectors
□ Document naming conventions for team consistency
Phase 3: Enterprise Scale (If Applicable)
□ Implement TypeScript type-safe selectors
□ Add namespace prefixes for micro-frontends
□ Add data-variant for A/B test tracking
□ Configure E2E tools to use data-element
```
### Selector Evolution Strategy
When you need to change selectors:
1. **Add new selector alongside old** (don't remove immediately)
2. **Update Fullstory defined elements** to use new selector
3. **Verify data continuity** in Fullstory dashboards
4. **Remove old selector** after confirming migration
---
## REFERENCE LINKS
### Fullstory Documentation
- **CSS Selectors in Search**: https://help.fullstory.com/hc/en-us/articles/360020623294
- **Defined Elements**: https://help.fullstory.com/hc/en-us/articles/360020828113
- **Element Properties Guide**: ../core/fullstory-element-properties/SKILL.md
### Testing Tool Integration
- **Cypress Best Practices (Selecting Elements)**: https://docs.cypress.io/guides/references/best-practices#Selecting-Elements
- **Playwright Locators**: https://playwright.dev/docs/locators
- **Testing Library Queries**: https://testing-library.com/docs/queries/about
### Accessibility & AI
- **WAI-ARIA Authoring Practices**: https://www.w3.org/WAI/ARIA/apg/
- **MDN: Using Data Attributes**: https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
### Historical Context
These skills consolidate and extend patterns from:
- `fullstorydev/eslint-plugin-annotate-react` (React-specific)
- `fullstorydev/fullstory-babel-plugin-annotate-react` (Build-time injection)
The manual approach in this skill is more flexible and works across all frameworks.
---
_This skill provides a universal, future-proof pattern for stable selectors that works in any framework. Optimized for Fullstory analytics, E2E testing, and AI-powered Computer User Agents. No external plugins required._