`
```
### Color Contrast
**Ensure sufficient contrast**:
```css
/* ❌ Bad - low contrast (2.5:1) */
.label {
color: #999;
background: #fff;
}
/* ✅ Good - high contrast (7:1) */
.label {
color: #333;
background: #fff;
}
```
**Test with tools**:
- [Chrome DevTools Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/).
- [axe DevTools](https://www.deque.com/axe/devtools/).
- [WAVE browser extension](https://wave.webaim.org/extension/).
### Focus Management
**Visible focus indicators**:
```css
button:focus-visible {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
```
**Don't remove focus styles**:
```css
/* ❌ Bad */
*:focus {
outline: none;
}
/* ✅ Good - custom but visible */
*:focus-visible {
outline: 2px solid currentColor;
}
```
## Error Handling
### Graceful Degradation
**Handle missing callbacks**:
```typescript
handleClick() {
// Always use optional chaining
this.onButtonClicked?.({ label: this._label })
}
```
**Validate properties**:
```typescript
set items(value: unknown) {
if (!Array.isArray(value)) {
console.warn('items must be an array')
this._items = []
return
}
this._items = value
this.render()
}
```
### Error Boundaries
**Wrap error-prone code**:
```typescript
async loadData() {
try {
const data = await import('./heavy-library.js')
return data
} catch (error) {
console.error('Failed to load library:', error)
this.showError('Failed to load component')
return null
}
}
```
### User-Friendly Messages
**Show helpful errors**:
```typescript
// ❌ Bad
throw new Error('Invalid input')
// ✅ Good
console.error('Invalid items prop: expected array, got', typeof value)
this.showError('Unable to display items. Please contact support.')
```
## Testing
### Test Across Browsers
**Minimum browser support**:
- Chrome, Firefox, Safari, and Edge: two most recent versions of each.
**Use cross-browser testing**:
- [BrowserStack](https://www.browserstack.com).
- [Sauce Labs](https://saucelabs.com).
- Local testing with different browsers.
### Test Edge Cases
**Empty states**:
```typescript
it('handles empty items array', () => {
element.items = []
expect(element.querySelector('.task')).toBeNull()
})
```
**Invalid input**:
```typescript
it('handles invalid items gracefully', () => {
element.items = null as any
expect(() => element.items = null).not.toThrow()
})
```
**Large datasets**:
```typescript
it('handles 1000 items', () => {
const items = Array.from({ length: 1000 }, (_, i) => ({
id: String(i),
title: `Task ${i}`,
completed: false,
}))
element.items = items
expect(element.querySelectorAll('.task')).toHaveLength(1000)
})
```
### Automated Testing
**Set up automated testing**:
```yaml
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
- run: npm run lint
```
## Deployment
### Pre-Deployment Checklist
Before uploading to Unqork, verify the following:
- [ ] All tests pass (`npm test`).
- [ ] Linting passes (`npm run lint`).
- [ ] Build succeeds (`npm run build:prod`).
- [ ] Bundle size is acceptable (< 100KB gzipped).
- [ ] Tested in dev harness (`npm run preview`).
- [ ] Documented usage and properties.
- [ ] Version updated — `npm run build:prod` bumps the patch version automatically; bump major or minor manually in `package.json` for breaking or feature changes.
- [ ] CHANGELOG updated.
### Staging First
**Deploy to staging before production**:
1. Upload to the staging environment.
2. Test in real modules.
3. Verify integration with other components.
4. Get stakeholder approval.
5. Deploy to production.
### Monitor After Deployment
**Check for issues**:
- Browser console errors.
- Performance regressions.
- User feedback.
- Error tracking tools like [Sentry](https://sentry.io).
## Common Pitfalls
### Avoid DOM Manipulation Outside the Component
**Don't modify the outer DOM**:
```typescript
// ❌ Bad - manipulates platform DOM
document.querySelector('.unqork-module').classList.add('custom')
// ✅ Good - modifies own DOM
this.querySelector('.my-component').classList.add('active')
```
### Don't Block the Main Thread
**Avoid heavy operations**:
```typescript
// ❌ Bad - blocks UI
function processLargeDataset(items: Item[]) {
return items.map(complexCalculation)
}
// ✅ Good - async with batching
async function processLargeDataset(items: Item[]) {
const batchSize = 100
const results = []
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize)
results.push(...batch.map(complexCalculation))
// Yield to browser
await new Promise(resolve => setTimeout(resolve, 0))
}
return results
}
```
### Clean Up Resources
**Always remove event listeners in** `disconnectedCallback()`:
Without cleanup, event listeners keep references to the component instance, causing memory leaks as the browser cannot reclaim unused memory.
```typescript
private handleIncrement = () => {
this._count++
this.render()
}
connectedCallback() {
// Commands are namespaced as :
this.addEventListener('myComponent:increment', this.handleIncrement)
}
disconnectedCallback() {
// Always clean up event listeners
this.removeEventListener('myComponent:increment', this.handleIncrement)
}
```
**Framework-specific cleanup**:
**React**
```typescript
useEffect(() => {
const host = containerRef.current?.getRootNode()?.host
if (!host) return
const handleIncrement = () => {
setCount(c => c + 1)
}
host.addEventListener('myComponent:increment', handleIncrement)
// Cleanup function automatically called on unmount
return () => {
host.removeEventListener('myComponent:increment', handleIncrement)
}
}, [])
```
**Vue**
```typescript
```
**Angular**
```typescript
export class MyComponent implements {
private hostElement: HTMLElement | null = null
ngAfterViewInit() {
this.hostElement = this.elementRef.nativeElement.getRootNode()?.host
if (!this.hostElement) return
this.hostElement.addEventListener('myComponent:increment', this.handleIncrement)
}
ngOnDestroy() {
if (this.hostElement) {
this.hostElement.removeEventListener('myComponent:increment', this.handleIncrement)
}
}
private handleIncrement = () => {
this.count++
}
}
```
**Lit**
```typescript
export class MyComponent extends LitElement {
private handleIncrement = () => {
this._count++
}
override connectedCallback() {
super.connectedCallback()
this.addEventListener('myComponent:increment', this.handleIncrement)
}
override disconnectedCallback() {
super.disconnectedCallback()
this.removeEventListener('myComponent:increment', this.handleIncrement)
}
}
```
**Cancel async operations**:
```typescript
private abortController: AbortController | null = null
async fetchData() {
this.abortController = new AbortController()
try {
const response = await fetch(url, {
signal: this.abortController.signal
})
return response.json()
} catch (error) {
if (error.name === 'AbortError') {
console.log('Fetch cancelled')
}
}
}
disconnectedCallback() {
this.abortController?.abort()
}
```
## Summary
**Key Takeaways**:
- Keep bundles small by splitting your bundle and loading code on demand.
- Validate use cases before creating BYO components.
- Prevent cross-site scripting (XSS) attacks and validate all inputs.
- Make components accessible through ARIA labels, keyboard navigation, and color contrast.
- Test thoroughly across browsers and edge cases.
- Document usage and maintain version history.
- Monitor performance and errors after deployment.
**Next Steps**:
- Review [Styling Guide](/unqorkai/docs/styling-guide-byo) for CSS best practices.
- Review [When to Use BYO](/unqorkai/docs/bring-your-own) guidelines.
- Check [FAQ](/unqorkai/docs/faq-byo) for common questions.
- See [Troubleshooting](/unqorkai/docs/troubleshooting-byo) for issues.
---
**Changelog**
| Date | Change |
| --- | --- |
| 2026-09-30 | Replaced `npx byo preview` with `npm run preview`; removed hardcoded API key example; updated command event names to namespaced format across all framework examples; fixed em dash in browser support list; added blank lines before lists; fixed broken when-to-use links to point to Overview (EN-8151). |
| 2026-05-01 | Initial publication |
The Document Object Model (DOM) is the data representation of objects that form the structure and content of a document on the web. The DOM represents the page.
Authentication secrets are a value of confidential information. For example, a password, security token, or cryptographic key, that is used to verify a user's identity during the authentication process to grant access to a system or application.
APIs (application programming interfaces) are a set of protocols and definitions developers use to build and integrate application software. APIs act as the connective tissue between products and services.
A key-value pair is a fundamental data structure where a unique "key" is associated with a specific "value," used for organizing and retrieving data efficiently, like in dictionaries or databases.
Cascading Style Sheets (CSS) is a style sheet language used for presenting how a HTML or XML document looks to end-users.
Also known as Unqork Users, or Designer Users; is anyone who is inside the Unqork platform.
A functionality of a software. For example, Copy and Paste is a feature of a computer's operating system.
AngularJS is a JavaScript framework added to an HTML page with a