Articles

Articles from Chunk Creations — our expertise and insights on the latest trends in the industry put to words. All articles available also on Medium

Lessons Learned: NPM packages publishing and API authorization

While refactoring @creation-ui/react to version 15, I tried to publish a new version to npm locally and couldn’t due to NPM auth errors. Here is how it went: $ npm publish npm WARN ignoring workspace config at /Users/username/GitHub/creation-ui-react/packages/ui/.npmrc npm notice npm notice 📦 @creation-ui/react@15.0.0 npm notice === Tarball Contents === npm notice 2.1kB CHANGELOG.md npm notice 1.1kB LICENSE npm notice 1.8kB README.md npm notice 918B dist/index.css npm notice 50.0kB dist/index.d.mts npm notice 50.0kB dist/index.d.ts npm notice 355.1kB dist/index.js npm notice 1.0MB dist/index.js.map npm notice 347.2kB dist/index.mjs npm notice 1.0MB dist/index.mjs.map npm notice 1.3kB dist/theme.css npm notice 2.2kB package.json npm notice === Tarball Details === npm notice name: @creation-ui/react npm notice version: 15.0.1 npm notice filename: creation-ui-react-15.0.0.tgz npm notice package size: 547.9 kB npm notice unpacked size: 2.9 MB npm notice shasum: 769ad59ba78310274a8a34c0933cad161f2ad483 npm notice integrity: sha512-rxDCX60g0xg5Z[…]dQiQwVoyqPIOg== npm notice total files: 12 npm notice npm notice Publishing to https://registry.npmjs.org with tag latest and public access npm ERR! code E404 npm ERR! 404 Not Found - PUT https://registry.npmjs.org/@creation-ui%2freact - Not found npm ERR! 404 npm ERR! 404 '@creation-ui/react@15.0.0' is not in this registry. npm ERR! 404 npm ERR! 404 Note that you can also install from a npm ERR! 404 tarball, folder, http url, or git url. npm ERR! A complete log of this run can be found in: /Users/username/.npm/_logs/2025–03–27T14_55_04_665Z-debug-0.log First things first When I started working with the NPM API a few years back, I learned that 404 is a bit misleading, as in fact it is 401 Unauthorized. The whole 401 as 404 thing in systems is really a design decision where any resource that hasn’t been found with the current privilege level is indeed a not-found resource, resulting in the 404 error code. It is a common design pattern in the frontend: accessing /user-is-private-route while not logged in? “Well, we didn’t find such a route, sorry mate ¯\\\_(ツ)\_/¯” Everywhere else it’s a bit too close to “security through obscurity” for my liking. But I digress. The important thing is the NPM said, You Shall Not Pass! or as cool kids say 401 Not Authorized. This happened despite that I had a token set up in .env that .npmrc file should catch on. I knew it could be done with an extra CLI command, but I couldn’t be bothered to check it out. # .npmrc auto-install-peers=true strict-peer-dependencies=false @creation-ui:registry=https://registry.npmjs.org //registry.npmjs.org/:_authToken=${NPM_TOKEN_AUTOMATION} To Auth or Not to Auth But that wasn’t the only way to get authorized. So I tried thenpm login. After successfully logging in on a browser, I still got the same error message. Bugged out as I was, it suddenly occurred to me that I can actually check if I’m logged in by running the `npm whoami` command: username@machine ui % npm whoami npm WARN ignoring workspace config at /Users/username/GitHub/creation-ui-react/packages/ui/.npmrc npm ERR! code E401 npm ERR! 401 Unauthorized - GET https://registry.npmjs.org/-/whoami npm ERR! A complete log of this run can be found in: /Users/username/.npm/_logs/2025–03–27T14_55_31_687Z-debug-0.log npm ERR! code E401 npm ERR! Unauthorized - please log in to your npm account to publish this package. Crazy right? At that point I knew that: - ✅ I logged in successfully - ❌ NPM API says I’m not logged in - ❌ NPM_TOKEN_AUTOMATION is a undefined It was clear to me that how NPM auth works is not clear at all. It seemed that NPM ignores which user is logged in and takes _authToken from the .npmrc file first. After closer inspection I figured out that token at time had value set to ”${NPM_TOKEN_AUTOMATION}” because of how I set up my .npmrc file. Removing this line resolved the issue for me: //registry.npmjs.org/:_authToken=${NPM_TOKEN_AUTOMATION} After that I was able to publish my package to NPM. Conclusion Where are the docs for that, @npm!? 👀 Originally published at https://www.pawelkrystkiewicz.pl on March 27, 2025.
PK

Paweł Krystkiewicz

March 27, 2025

Tailwind 4 Dark Mode Dynamic Theme

Learn how to implement a dynamic dark mode theme in Tailwind CSS 4 using the new theme in CSS config. January 25, 2025 So the Tailwind 4 was released. The biggest breaking change was the introduction of new theme configuration method—you can only do it in CSS now. Here is an official example: @import 'tailwindcss'; @theme { --font-display: 'Satoshi', 'sans-serif'; --breakpoint-3xl: 1920px; --color-avocado-100: oklch(0.99 0 0); --color-avocado-200: oklch(0.98 0.04 113.22); --color-avocado-300: oklch(0.94 0.11 115.03); --color-avocado-400: oklch(0.92 0.19 114.08); --color-avocado-500: oklch(0.84 0.18 117.33); --color-avocado-600: oklch(0.53 0.12 118.34); --ease-fluid: cubic-bezier(0.3, 0, 0, 1); --ease-snappy: cubic-bezier(0.2, 0, 0, 1); /* ... */ } The problem with this approach is that there is no easy way to define the dark mode theme here, as complained on their GitHub. Solution This is how I solved this issue in my React components system, @creation-ui/react using @variant directive: @import 'tailwindcss'; @variant dark (&:where(.dark, .dark *)); :root { --text-primary: oklch(0 0 0); --text-secondary: oklch(0.556 0 0); --background-primary: oklch(0.99 0 0); --background-secondary: oklch(0.985 0 0); --border: oklch(0.87 0 0); @variant dark { --text-primary: oklch(0.97 0 0); --text-secondary: oklch(0.87 0 0); --background-primary: oklch(0.269 0 0); --background-secondary: oklch(0.371 0 0); --border: oklch(0.35 0 0); } }@theme { --color-primary: oklch(60.48% 0.2165 257.21); --color-warning: oklch(77.97% 0.1665 72.45); --color-error: oklch(66.16% 0.2249 25.88); --color-success: oklch(75.14% 0.1514 166.5); --color-text-primary: var(--text-primary); --color-text-secondary: var(--text-secondary); --color-background-primary: var(--background-primary); --color-background-secondary: var(--background-secondary); --color-border: var(--border); } Basically in :root we define both our light and dark color variables, where @variant dark decides of the variable values. Because Tailwind generates color CSS classes (text-[color]-value, bg-[color]-value, border-[color]-value, etc.) from --color- variables, we can use them in our React components like so: const Button = ({ children, ...props }) => { return ( ) } And that’s it! Now you can use a dynamic light/dark theme in your app, and it will automatically switch to dark mode when you set dark variant (here: a .dark class on, e.g. body element). Originally published at pawelkrystkiewicz.pl 25, 2025.
PK

Paweł Krystkiewicz

January 25, 2025

Building a Smart Truncation Detection Hook for React

Learn how to create a reusable React hook that detects text truncation in UI elements, enabling intelligent tooltips and responsive design adjustments The Truncation Detection Problem In modern UIs, we often truncate text with CSS when it exceeds container bounds: .truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } This is cool and all, but sometimes we need to keep those truncated strings helpful and communicative for our users. To that end, it would be helpful to know whether our string has been truncated or not. This knowledge could open such opportunities as This knowledge could open such opurtunites as: Showing tooltips only when content is truncated Dynamically adjusting layouts Providing expand/collapse functionality Can we actually detect that? Yes, yes we can! A very rudimentary attempt could be made by checking element dimensions: const isTruncated = element.scrollWidth > element.clientWidth While this works OK, it has several limitations: Doesn’t respond to window resizing Requires “manual” DOM access Definitely lacks React lifecycle awareness Doesn’t handle edge cases (like flex containers) To make this work the best with React, we definitely could use a hook. Solution For this to work, we need a hook with: Type safety with generics ResizeObserver for responsiveness Simple API import { RefObject, useEffect, useRef, useState } from 'react' interface UseDetectedTruncation { ref: RefObject isTruncated: boolean }export const useDetectedTruncation = < RefType extends HTMLElement, >(): UseDetectedTruncation => { const [isTruncated, setIsTruncated] = useState(false) const elementRef = useRef(null) const checkTruncation = () => { const element = elementRef.current if (!element) return // Check both width and height for multi-line truncation const isWidthTruncated = element.scrollWidth > element.clientWidth const isHeightTruncated = element.scrollHeight > element.clientHeight setIsTruncated(isWidthTruncated || isHeightTruncated) } useEffect(() => { const element = elementRef.current if (!element) return // Initial check checkTruncation() // Set up observation const resizeObserver = new ResizeObserver(checkTruncation) resizeObserver.observe(element) // MutationObserver for content changes const mutationObserver = new MutationObserver(checkTruncation) mutationObserver.observe(element, { childList: true, subtree: true, characterData: true, }) return () => { resizeObserver.disconnect() mutationObserver.disconnect() } }, []) return { ref: elementRef, isTruncated } } Practical Usage Here’s how to create a smart tooltip component using our hook: import { Tooltip, type TooltipProps } from '@your-ui-library' import { twMerge } from 'tailwind-merge' interface SmartTooltipProps extends React.HTMLAttributes { tooltipProps: Omit content: string } export const SmartTooltip = ({ tooltipProps, content, children, className, ...props }: SmartTooltipProps) => { const { isTruncated, ref } = useDetectedTruncation() return ( {children || content} ) } Performance Considerations Debounce Observations: For frequently resizing elements, consider debouncing the checks: const debouncedCheck = useDebounce(checkTruncation, 100) 2. Selective Observation: Only observe necessary attributes: resizeObserver.observe(element, { box: 'content-box' }) 3. Cleanup: Properly disconnect observers in the cleanup function to prevent memory leaks. Testing Strategies Verify the hook works in different scenarios: Static truncated text Dynamically loaded content Responsive layout changes Multi-line truncation (line-clamp) Nested scrolling containers describe('useDetectedTruncation', () => { it('detects horizontal truncation', () => { const { result } = renderHook(() => useDetectedTruncation()) render(
Long text that should truncate
, ) expect(result.current.isTruncated).toBe(true) }) it('ignores non-truncated content', () => { const { result } = renderHook(() => useDetectedTruncation()) render(
Short text
, ) expect(result.current.isTruncated).toBe(false) }) }) Going forward To make it even sexier, we could consider adding the following features: https://medium.com/media/d37d1c060816395a578aba437cc12a89/href Conclusion The useDetectedTruncation hook provides a clean, reusable solution for a common UI challenge. By encapsulating the detection logic, we can: Create more accessible interfaces Build smarter components Reduce unnecessary tooltip clutter Make our UIs more responsive to content changes. Originally published at https://www.pawelkrystkiewicz.pl on November 20, 2024.
PK

Paweł Krystkiewicz

November 20, 2024

Managing UI Decision Complexity—From Boolean Soup to Business Rules

In software development, we often need to check multiple conditions in one step, be it a frontend component or a backend function. In frontend development, it will mostly decide about UI state. Let’s say there are multiple conditions coming from different parts of the system: user permission levels, user subscription plans, entity states, etc. Usually frontend code grows with the complexity of product’s business logic. This often starts as a simple conditional statement like so: import React from 'react' export const App = () => { // authentication logic here const authenticated = useIsAuth() // decide which view to show return authenticated ? : } This is very basic conditional logic for displaying private routes only to authenticated (logged in) users. When things get complicated The previous example was very rudimentary. Complexity can grow very quickly as business requirements grow and evolve in a Scrum environment. It’s highly likely more conditions will be added to the app’s business logic and be required to check for proper UI display. For this let’s use example of Meta’s Messenger app and the single message entity. First, we define a simple Message entity with all its relevant properties and context: type Message = { id: string senderId: string recipientId: string sentAt: Date deliveredAt?: Date seenAt?: Date deleted: boolean failed: boolean attachmentsCount: number isPinned: boolean isEdited: boolean isReply: boolean isGroupMessage: boolean currentUserId: string } Below is a table of conditions that control how the UI behaves for each message:
Visual mapping If isFailedToSend → show red error icon and retry option. If isDelivered but not isSeen → show double-check icon in gray. If isSeen → show double-check icon in blue. If canEdit → show edit button. If showPinnedIcon → display pin in corner. If showReplyPreview → show small reply preview UI. Now let’s see how this could be implemented without any clever refactors—just growing the codebase overtime. import React from 'react' import { Check, DoubleCheck, Pin, WarningTriangle, Attachment, EditPencil, Reply, } from 'iconoir-react' import { Message } from './types' type MessageProps = { message: Message userIsGroupAdmin: boolean }export const OldFlagsMessage: React.FC = ({ message, userIsGroupAdmin, }) => { const isSent = !!message.sentAt && !message.failed const isDelivered = !!message.deliveredAt && !message.seenAt const isSeen = !!message.seenAt const isFailedToSend = message.failed const canEdit = message.senderId === message.currentUserId && !message.deleted && !message.failed return // render From terrible mess to terribly clever The code above is less than ideal, especially around readability. As a first step, one could certainly refactor all those checks into hooks. But let’s go deeper into this refactor, as business requirements will certainly grow, as the only constant in life (and especially in software development) is change. Death and taxes are just a derivative of that. So we want to create a future-proof refactor that could possibly be a more robust solution that, if necessary, could be used in other places of the system. The latter is an especially good requirement because making this a bit more top-level and detached from the very business logic allows sharing responsibility for this code among many team members. Easing code maintenance and benefiting from sharing ideas around this solution. There are few things those conditions have in common: result is a boolean flag they can be isolated—they are not interdependent. they depend on a limited source of truth—deciding data passed to the component is a limited set. All this tells us there is a solution to this problem that could be expressed as function a => (payload: LimitedDataSet) => boolean If we could define all boolean flags as such functions, we could include them in a data set that could run each function with the same payload and collect results for each flag. If this is not clever, then I hate React. So, inspired by eslint rules and cva props evaluation, I created a simple engine that takes an array of rules (not nested) and runs each function with provided props. Each Rule is represented with its name, and the function that returns boolean. The function evaluates the incoming props and returns a boolean value: a Flag. export type Rules< A = any, K extends string | number | symbol = string, > = Record boolean> export type Flags = Record< K, boolean > Modeling these conditions as a rules object With the above pattern, we can create a set of functions—in this case in an object: //basically component props in our case export type MessageRulesArgs = { message: Message userIsGroupAdmin: boolean } // this is definiton of our required flags export type MessageRuleSet = { isSent: boolean isDelivered: boolean isSeen: boolean canEdit: boolean showPinnedIcon: boolean showEditedIndicator: boolean showReplyPreview: boolean showAttachmentIcon: boolean showFailedIcon: boolean showDeleteButton: boolean } export type MessageRules = Rules export const messageRules: MessageRules = { isSent: ({ message: m }) => !!m.sentAt && !m.failed, isDelivered: ({ message: m }) => !!m.deliveredAt && !m.seenAt, isSeen: ({ message: m }) => !!m.seenAt, canEdit: ({ message: m }) => m.senderId === m.currentUserId && !m.deleted && !m.failed, showPinnedIcon: ({ message: m }) => m.isPinned, showEditedIndicator: ({ message: m }) => m.isEdited, showReplyPreview: ({ message: m }) => m.isReply, showAttachmentIcon: ({ message: m }) => m.attachmentsCount > 0, showFailedIcon: ({ message: m }) => m.failed, showDeleteButton: ({ message: m }) => m.senderId === m.currentUserId && !m.deleted, } With rules defined, the remaining thing to do is run and evaluate the result with our data. For this I came up with this little helper: import type { Flags, Rules } from './types' export const applyRules = < A = any, K extends string | number | symbol = string, >( rule: Rules, args: A, ): Flags => { return Object.keys(rule).reduce((flags: Flags, key: string) => { const ruleFn = rule[key as K] if (ruleFn) { flags[key] = ruleFn(args) } return flags }, {}) as Flags } Then, to use it inside a component, we can create a hook: import { useDeepCompareMemo } from 'use-deep-compare' import { applyRules } from './apply-rules' import { Rules } from '../types' export const useRules = < RuleSet extends Rules, Args extends Record, >( rules: RuleSet, args: Args, ): Record => { // use a deep compare memo to memoize the result regardless of depth return useDeepCompareMemo( () => applyRules(rules, args), [rules, args], ) } Final usage in React component: import { Attachment, Check, DoubleCheck, EditPencil, Pin, Reply, WarningTriangle, } from 'iconoir-react' import React from 'react' import { messageRules } from './rules/rules' import { useRules } from './rules/rules-hook' import { Message, MessageRules, MessageRulesArgs } from './types' type MessageProps = { message: Message userIsGroupAdmin: boolean } export const WithRulesMessage: React.FC = ({ message, userIsGroupAdmin, }) => { const { isSent, isDelivered, isSeen, canEdit, showPinnedIcon, showEditedIndicator, showReplyPreview, showAttachmentIcon, showFailedIcon, showDeleteButton, } = useRules(messageRules, { message, userIsGroupAdmin, }) return //render } Benefits of this approach Clarity: Each condition is named and easy to understand. Reusability: Components can import and use conditions without duplication. Testability: Each rule can be unit-tested independently. Extensibility: Adding new rules is simple and low-risk. Conclusion If you find yourself with growing UI complexity and a web of conditions, consider abstracting those checks into a rules object or lightweight engine. It brings structure, clarity, and scalability to what would otherwise become a nightmare of conditionals and spaghetti code. Originally published at https://www.pawelkrystkiewicz.pl on October 3, 2024.
PK

Paweł Krystkiewicz

October 3, 2024

Framer Motion: Animating Height Transitions in React

How to create smooth height animations for collapsible content using Framer Motion and dynamic duration calculations based on content size. The Height Animation Problem Animating height transitions is notoriously tricky in CSS. Unlike opacity or transforms, you can’t simply transition from height: 0 to height: auto. The browser needs concrete values to interpolate between: /* This doesn't work */ .collapsible { height: 0; transition: height 0.3s; }.collapsible.open { height: auto; /* Can't animate to 'auto' */ } Common workarounds involve JavaScript to calculate heights, max-height hacks that create awkward timing, or fixed heights that break with dynamic content. None of these are ideal. For expandable sections, accordions, dropdowns, or any collapsible UI, we need smooth height transitions that work with dynamic content of any size. Solution Framer Motion combined with the react-use library's useMeasure hook gives us a clean solution. We measure the content's actual height and animate to that specific value, with smart duration scaling based on content size. import type { FC, ReactNode } from 'react' import { useMeasure } from 'react-use' import { motion } from 'framer-motion' interface AnimateHeightProps { isVisible: boolean ease?: string duration?: number className?: string variants?: { open: object collapsed: object } children: ReactNode }export const AnimateHeight: FC = ({ duration, ease, variants, isVisible, children, ...other }) => { const [ref, { height }] = useMeasure() return (
{children}
) }/** * Get the duration of the animation depending upon * the height provided. * @param {number} height of container */ const getAutoHeightDuration = (height: number) => { if (!height) return 0 const constant = height / 36 return Math.round((4 + 15 * constant ** 0.25 + constant / 5) * 10) }AnimateHeight.defaultProps = { ease: 'easeInOut', variants: { open: { opacity: 1, height: 'auto', }, collapsed: { opacity: 0, height: 0 }, }, } How It Works useMeasure Hook: Tracks the actual rendered height of the content in real-time. When content changes, the height updates automatically. Motion Variants: Define two states: open - Full height with opacity 1 collapsed - Zero height with opacity 0 Dynamic Duration: The getAutoHeightDuration function calculates animation timing based on content height. Taller content gets longer animations, preventing jarring fast transitions for large sections. overflow-hidden: Critical for the effect - hides content as the container shrinks to zero height. inherit=false: Prevents inheriting animation variants from parent motion components, keeping this animation independent. The Duration Formula The getAutoHeightDuration function deserves attention: const getAutoHeightDuration = (height: number) => { if (!height) return 0 const constant = height / 36 return Math.round((4 + 15 * constant ** 0.25 + constant / 5) * 10) } This formula creates a non-linear relationship between height and duration: Small heights (50px): ~150ms Medium heights (200px): ~250ms Large heights (500px): ~400ms Extra large (1000px): ~550ms Breaking Down the Math Let’s dissect that return statement: Math.round((4 + 15 * constant ** 0.25 + constant / 5) * 10) Step 1: Normalize the height const constant = height / 36 This converts pixels to a normalized scale. For example, 360px becomes 10, making the math more manageable. This is totally arbitrary; pick what works best for your use case. Step 2: Three components create the curve The formula has three parts that add together: Base duration: 4 Ensures even tiny elements get at least 40ms (after × 10) 2. Diminishing growth: 15 * constant ** 0.25 The ** 0.25 is a fourth root creates sublinear growth This is the key: doubling height doesn’t double duration Prevents massive elements from having sluggish animations 3. Linear component: constant / 5 Adds some proportional scaling Balances out the diminishing returns from component 2 Step 3: Scale to milliseconds Math.round((...) * 10) Multiply by 10 to convert to milliseconds and round for clean values. Why This Curve? The fourth root (** 0.25) is the secret sauce. Compare linear vs fourth-root scaling: https://medium.com/media/370d5ec67cac76057bbfef0fc0a9dee3/href Without the fourth root, large collapsible sections would take almost a second to animate, feeling slow and unresponsive. The formula keeps animations snappy regardless of content size while still giving taller content enough time to feel smooth. For an extra crispy effect, you could clamp this value to keep it within the desired range. Math.max(Math.min(calculated, 40, 350)) At the end of day, you can override this by passing a fixed duration prop when you need consistent timing across all heights. Practical Usage Simple accordion: const Accordion = ({ title, content }) => { const [isOpen, setIsOpen] = useState(false) return (
{content}
) } Custom animation variants: Fixed duration for consistent timing: {children} Why This Approach Works Automatic measurement: No manual height calculations needed. The component adapts to content changes automatically. Smooth animations: Unlike max-height hacks, the animation duration matches the actual height transition. Flexible: Override defaults when needed while keeping sensible behavior out of the box. Dynamic content friendly: If content height changes while open, the animation adjusts seamlessly. Performance Considerations While this approach is generally performant, be aware: Height animations trigger layout recalculation: Unlike transforms, animating height is not GPU-accelerated. For numerous simultaneous animations, this can impact performance. Measurement overhead: useMeasure uses ResizeObserver, which is efficient but adds overhead. For hundreds of collapsible items, consider virtualization. Alternative for performance-critical cases: If you need many height animations, consider animating scaleY transform instead: // More performant but content gets squished The tradeoff is that scaleY squishes content during animation, while height animations maintain readable content throughout. Comparison with AnimatePresence This component differs from the AnimateAppearance pattern: https://medium.com/media/4984edf8d13dab938e97f362dc07fb73/href Use AnimateHeight when content should remain in the DOM (for SEO, form state, etc.) and you want vertical expand/collapse. Use AnimatePresence when components are truly conditional and should be unmounted. Common Use Cases FAQ accordions: { faqs.map(faq => ( {faq.answer} )) } Filter panels: Form sections: Conclusion The AnimateHeight component solves one of CSS's most annoying limitations by combining Framer Motion's animation capabilities with real-time height measurement. By automating the measurement and providing smart duration scaling, we get: Smooth height transitions without CSS hacks Automatic adaptation to dynamic content Customizable timing when needed Clean, reusable animation logic Next time you need a collapsible section, skip the CSS gymnastics and reach for this component. Your users will appreciate the smooth, professional transitions. Originally published at https://www.pawelkrystkiewicz.pl on February 10, 2024.
PK

Paweł Krystkiewicz

February 10, 2024

The Magic of Bookmarklets

A deep dive into bookmarklets—how they work, why they work, and how to create your own JavaScript-powered browser shortcuts. How Bookmarklets Work: Injecting Code into Any Webpage In the previous article, I showed you this mysterious one-liner: javascript:(function(){var s=document.createElement('style');s.innerHTML='*{background:#000!important;color:#0f0!important;outline:solid #f00 1px!important}';document.head.appendChild(s);})(); Save it as a bookmark, click it, and suddenly your page has debug outlines everywhere. But how does this actually work? And why does JavaScript code live in a bookmark? Let’s break it down. What is a Bookmarklet? A bookmarklet is executable JavaScript code stored as a browser bookmark. Instead of navigating to a URL, clicking the bookmark executes code on the current page. The secret is the javascript: protocol prefix. According to MDN, these are "fake navigation targets that execute JavaScript when the browser attempts to navigate." // Normal bookmark https://example.com // Bookmarklet javascript:alert('Hello from a bookmark!') That’s it. Any valid JavaScript after javascript: will run in the context of the current page. How the Browser Processes javascript: URLs When you execute a javascript: URL, the browser: Parses and executes the script Evaluates its completion value (similar to eval()) If the completion value is a string, treats it as HTML and navigates to it If it’s not a string, just execute the code without navigation. This is why simple expressions can replace your page content, while IIFEs (which return undefined) don't. Breaking Down the Debug Bookmarklet Let’s dissect the CSS debug bookmarklet piece by piece: 1. The Protocol Prefix javascript: Tells the browser, “Execute this as JavaScript; don’t navigate anywhere.” 2. The IIFE Wrapper (function(){ // code here })(); An Immediately Invoked Function Expression (IIFE). This pattern: Keeps variables scoped (doesn’t pollute the global namespace) Executes immediately Returns nothing (important—we don’t want to navigate) Without the IIFE, any return value would replace the page content. Try this: javascript:'Hello' Your page gets replaced with the text “Hello.” The IIFE prevents this. 3. Creating a Style Element var s = document.createElement('style'); Creates a new