$0 and Friends: The DevTools Shortcuts You’re Missing
Chrome Devtools Edition
You’re debugging. You inspect an element. Now you want to test something on it in the console.
So you do this:
document.querySelector('.some-complicated-selector-you-have-to-type')Or worse, you right-click, “Copy selector”, paste it into console. Every. Single. Time.
There’s a better way. And it’s been in DevTools for over a decade.
$0: The Element You Just Selected
Click an element in the Elements panel. Switch to Console. Type:
$0That’s it. That’s the element you just selected.
$0.style.background = 'red'; // Change background
$0.classList.add('debug'); // Add class
$0.remove(); // Delete it
$0.textContent = 'Changed!'; // Update textNo selectors. No copying. Just $0.
It gets better:
$1= previously selected element$2= element before that$3= element before that$4= element before that
You have a history of the last 5 elements you clicked.
$0 // Current selection
$1 // Previous selection
$2 // Selection before that
// etc.Testing interactions between elements? Switch between them instantly:
// Select parent element, then child
$1.contains($0) // true
// Compare two buttons you clicked
$0.textContent === $1.textContent
// Move element from one parent to another
$1.appendChild($0)This is way faster than typing selectors.
$$: querySelectorAll That Doesn’t Suck
Need all buttons on the page?
The old way:
Array.from(document.querySelectorAll('button'))The shortcut:
$$('button')It returns an actual array. Not a NodeList. An array.
$$('button').map(btn => btn.textContent)
$$('img').filter(img => !img.alt)
$$('a').forEach(link => console.log(link.href))No Array.from(). No [...nodeList] spread. Just arrays.
Real examples from my debugging:
// Find all images without alt text
$$('img').filter(img => !img.alt)
// Get all external links
$$('a').filter(a => a.hostname !== location.hostname)
// List all form inputs with values
$$('input').map(input => ({
name: input.name,
value: input.value
}))
// Find empty elements
$$('*').filter(el => !el.textContent.trim())
// Count elements by tag
$$('*').reduce((counts, el) => {
counts[el.tagName] = (counts[el.tagName] || 0) + 1;
return counts;
}, {})These would all be painful with querySelectorAll.
$: querySelector (Usually)
$('button') // First button on page
$('.header') // First element with class="header"
$('#app') // Element with id="app"It’s just document.querySelector() but shorter.
The catch: If jQuery is loaded, $ is jQuery. So this only works on pages without jQuery.
But honestly? In 2026, most modern apps don’t use jQuery. So this works most places.
When jQuery is loaded:
$('button') // Returns jQuery object, not elementWhen jQuery isn’t loaded:
$('button') // Returns elementYou can check:
typeof $ === 'function' && $.fn // jQuery is loaded
typeof $ === 'function' && !$.fn // DevTools $ is availablecopy(): Get Stuff Out of Console
You computed something complex in console. Now you need it in your editor.
const data = $$('a').map(a => ({
text: a.textContent,
url: a.href
}));
copy(data)The data is now in your clipboard as JSON. Paste it anywhere.
This is insanely useful for:
Extracting data from pages:
// All product prices on an e-commerce page
copy($$('.price').map(el => el.textContent))
// Navigation structure
copy($$('nav a').map(a => ({
text: a.textContent,
url: a.href
})))
// All images and their attributes
copy($$('img').map(img => ({
src: img.src,
alt: img.alt,
width: img.width,
height: img.height
})))Copying computed styles:
copy(getComputedStyle($0))Copying localStorage:
copy(localStorage)Exporting app state for debugging:
// In React DevTools
copy($r.state) // Copy component state
// Or in console
copy(window.__REDUX_STATE__)No more “take screenshot of console output” or manually transcribing data.
Combining Them All
This is where it gets powerful:
// Select an element in Elements panel, then:
$$('img', $0) // All images inside selected element$$() takes a second parameter: the root element to search from. Defaults to document, but you can pass anything.
Real debugging workflows:
// 1. Click a card component in Elements
// 2. Find all links inside it
$$('a', $0).map(a => a.href)
// 3. Copy to clipboard
copy($$('a', $0).map(a => a.href))Or backwards:
// 1. Find element in console
const form = $('form.signup');
// 2. Select it in Elements panel
inspect(form)The inspect() function opens Elements panel and highlights the element.
inspect($('header')) // Jump to header in Elements
inspect($$('button')[5]) // Jump to 6th buttonOther DevTools Shortcuts Nobody Uses
While we’re here:
$x() - XPath queries:
$x('//button') // All buttons via XPath
$x('//a[contains(@href, "github")]') // Links containing "github"I’ve never used this. But it exists.
$_ - Last evaluated expression:
2 + 2
// 4
$_
// 4
$_ * 2
// 8Useful for “do that again” without retyping.
keys() and values():
keys($0.dataset) // All data- attributes
values(localStorage) // All localStorage valuesShortcuts for Object.keys() and Object.values().
getEventListeners():
getEventListeners($0)Shows all event listeners on an element. Incredibly useful for “why isn’t this click working?”
// See all click handlers
getEventListeners($0).click
// See all event types registered
Object.keys(getEventListeners($0))monitorEvents() and unmonitorEvents():
monitorEvents($0, 'click') // Log every click on element
monitorEvents($0) // Log ALL events
unmonitorEvents($0) // Stop loggingBetter than adding addEventListener just to debug.
queryObjects():
queryObjects(Promise) // All Promise instances
queryObjects(Array) // All arrays
queryObjects(HTMLButtonElement) // All buttonsFind all instances of a constructor in memory. Great for memory leak debugging.
My Actual Debugging Workflow
Scenario: User reports button not working
Inspect the button (Elements panel)
Console:
getEventListeners($0)Check if click handler exists
Console:
monitorEvents($0, 'click')Click the button, watch events fire
Console:
$0.disabled(check if disabled)Console:
getComputedStyle($0).pointerEvents(check CSS)
No code changes. No console.logs. Just interactive debugging.
Scenario: Extract data from competitor’s site
Console:
$$('.product').map(p => ({ name: $('.title', p).textContent, price: $('.price', p).textContent }))Console:
copy(_)(copy last result)Paste into editor
Parse as needed
Scraping without writing a script.
Scenario: Why is this component re-rendering?
React DevTools: Select component
Console:
$r(selected React component)Console:
copy($r.props)Compare props between renders
Scenario: Find all memory leaks
// Take heap snapshot
// Select an object
// Console:
queryObjects($0.constructor)
// See all instances of that typeThe Ones That Break
$0 becomes null when:
You refresh the page
Element gets removed from DOM
You close DevTools
Always check if it exists:
$0 && $0.classList.add('debug')$$ doesn’t work in:
Service workers
Web workers
Extensions (sometimes)
It’s a DevTools console feature only. Not available in production code.
copy() limitations:
Circular references become
[Circular]Functions don’t serialize
DOM nodes become
{}
It’s JSON.stringify under the hood with some magic.
Why Nobody Teaches This
These shortcuts don’t work in production code. They’re DevTools-only.
So tutorials and courses don’t cover them. You can’t put $0 in a React component.
But who cares? You’re not shipping DevTools shortcuts to users. You’re using them to debug faster.
The overlap between “features that work in production” and “features that speed up debugging” is smaller than you think.
Browser Differences
Chrome/Edge:
Everything works
Most mature implementation
Firefox:
$0,$1, etc. work$$()works$()workscopy()worksEverything basically works
Safari:
Most shortcuts work
copy()is buggy sometimesGenerally less polished
The important ones ($0, $$) work everywhere.
My Take
I’ve been debugging JavaScript for 15 years. I learned about $0 in 2018. Five years too late.
Nobody teaches this. It’s not in MDN prominently. It’s not in tutorials. You stumble on it by accident or someone shows you.
But once you know it? Your debugging speed doubles.
No more typing document.querySelector() fifty times. No more copying selectors. No more Array.from(document.querySelectorAll()).
Just $0 and $$() and copy() and you’re done.
These shortcuts feel like cheating. They’re not in “real” JavaScript. They’re DevTools magic.
But that’s the point. DevTools exist to make debugging easier. Use the shortcuts.
Stop typing document.querySelector(). Start typing $0.
Try it right now:
Open DevTools on this page
Click any element in Elements panel
Switch to Console
Type:
$0.style.outline = '2px solid red'Watch the element get outlined
Then try:
$$('a').length // Count links
copy($$('a').map(a => a.href)) // Copy all URLsYou’ll never go back.
Reference card:
$0-$4 // Last 5 selected elements
$('selector') // querySelector
$$('selector') // querySelectorAll as array
copy(anything) // Copy to clipboard as JSON
inspect(element) // Jump to element in Elements panel
getEventListeners(element) // Show all event listeners
monitorEvents(element, 'event') // Log events
queryObjects(Constructor) // Find all instances
$_ // Last console result
keys(obj) // Object.keys shorthand
values(obj) // Object.values shorthandPrint this. Or just remember $0 and $$. That’s 90% of the value.


