chrome-ext-ui-react — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited chrome-ext-ui-react (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
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.
React works in extension popups, side panels, options pages, and content script injected UIs. Each context has unique constraints. The core challenges are: state dies with popup close, Shadow DOM is required for content script UI, and portaled components break in Shadow DOM.
Extension popups render from scratch on every toolbar click and die when they lose focus. Side panels persist longer but still need storage-backed state.
Never rely on React `useState` alone for persistent data. Always back state with chrome.storage:
function useChromeState<T>(key: string, defaultValue: T) {
const [value, setValue] = useState<T>(defaultValue);
const [loading, setLoading] = useState(true);
useEffect(() => {
chrome.storage.local.get({ [key]: defaultValue }).then((result) => {
setValue(result[key]);
setLoading(false);
});
const listener = (changes: Record<string, chrome.storage.StorageChange>, area: string) => {
if (area === 'local' && key in changes) setValue(changes[key].newValue);
};
chrome.storage.onChanged.addListener(listener);
return () => chrome.storage.onChanged.removeListener(listener);
}, [key]);
const set = async (newValue: T) => {
setValue(newValue);
await chrome.storage.local.set({ [key]: newValue });
};
return [value, set, loading] as const;
}Inject extension UI into web pages using Shadow DOM to prevent CSS pollution:
export default defineContentScript({
matches: ['<all_urls>'],
cssInjectionMode: 'ui',
main(ctx) {
const ui = createShadowRootUi(ctx, {
name: 'my-extension',
position: 'inline',
onMount(container) {
const root = createRoot(container);
root.render(<App />);
return root;
},
onRemove(root) { root.unmount(); },
});
ui.mount();
},
});React UI libraries (Radix UI, Headless UI, Shadcn) portal modals, dialogs, and tooltips to document.body. In content scripts, this forces them outside the Shadow DOM, stripping all styles. Configure the portal container:
import { PortalProvider } from '@radix-ui/react-portal';
function App({ container }: { container: HTMLElement }) {
return (
<PortalProvider container={container}>
<Dialog>{/* Portals inside Shadow DOM */}</Dialog>
</PortalProvider>
);
}React UI libraries use rem units that scale based on the host page's root font size. If a host page sets font-size: 10px, the extension UI appears broken. Convert at build time:
// postcss.config.ts
import remToPx from '@thedutchcoder/postcss-rem-to-px';
export default {
plugins: [remToPx({ baseValue: 16 })],
};Use vanilla DOM code for "Integrated UIs" — small buttons, text highlights, minor visual tweaks:
document.createElement() for simple elementsMutationObserver for monitoring dynamic SPAsReact is best for complex dashboards, side panels, and feature-rich popups.
title for screen readersgetByRole() and getByLabelText() as selectors — stable on third-party pagesaria-live regions for async state updatesaria-labelrole, tabindex="0", and keyboard event handlersinput.value changes don't trigger React; dispatch an input eventchrome.storage.onChanged and chrome.runtime.onMessage listeners~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.