iPadOS 18.0 Safari has an issue that the focus does not move to the webpage when the tab is created or activated until the webpage gets the focus manually by the webpage touched / clicked, one of the arrow keys (up, down, left, or right) pressed, or tab key pressed.
Hence, shortcuts in websites using keyboard events of the webpage do not work (e.g. gmail shortcuts do not work). As Vimlike uses keyboard events of the webpage, Vimlike only works once the webpage gets the focus manually by the webpage touched / clicked, one of the arrow keys (up, down, left, or right) pressed, or tab key pressed as a workaround.
iPadOS 18.1 beta 4 has resolved the problem and Vimlike works fine.
Vimlike works fine with MacOS 15 Sequoia.
VIMlike is Safari extension for the Vim-like keyboard experience. You can navigate a page and open a link with the keyboard shortcuts.
The latest version of Vimlike is available in AppStore:
3 Aug 2024
18 Mar 2024
16 Mar 2024
4 Mar 2024
15 Jan 2024
9 Jan 2024
1 Oct 2023
10 Sep 2023
20 Jun 2023
17 May 2023
20 Nov 2022
1 Nov 2022
26 Sep 2022
13 Sep 2022
16 Aug 2022
15 Aug 2022
17 Jul 2022
17 Jul 2022 • New command: ‘y y’ to copy the URL and ‘y t’ to duplicate the tab • Enhanced smooth scroll
10 Jul 2022
19 Jun 2022
28 May 2022
3 Feb 2022
26 Dec 2021
12 Dec 2021
settings.map('r', VLCommand.RELOAD);
Added new settings; ** Require Enter when opening the link in Console ** Ignore Keyboard Layout; to support the latin keyboards other than US-EN Qwerty (e.g. German). ** Prevent focus on page load
10 Oct 2021
4 Oct 2021
Vimlike does NOT collect any of your data. The only data Vimlike stores are your settings. These are only saved locally. No one, other than you, has access to this data.
Because of the way Vimlike works, the Vimlike extension needs to have permission to access to the website. This is needed because Vimlike has to add javascript to manage keyboard shortcuts and links to the web page. However, Vimlike never use these permissions to actually read the websites you visit, or to access your browsing history.
Vimlike does not store any information about the websites you’re visiting. Not even locally on your device.
In Safari Preference, you can Allow or Deny the extensions to specific websites. In case you want to be extra cautious to certain websites, you can enable or disable Vimlike to the specific websites only.
Key | Command |
---|---|
k | Scroll up |
j | Scroll down |
h | Scroll left |
l | Scroll right |
u | Half page up |
d | Half page down |
g g | Go to top of page |
shift + g | Go to bottom of page |
f | Toggle links |
shift + f | Toggle links (open a link in a new tab) |
tab or n | Toggle links / Move to next link or search result |
shift + tab or shift + n | Toggle links / Move to previous link or search result |
esc or ` or § (configurable in Settings) |
Back to Normal mode |
shift + h | Go back |
shift + l | Go forward |
q | Previous tab |
w | Next tab |
t | New tab |
x | Close tab |
shift + z | Reopen closed tab |
y y | Copy current URL to the clipboard |
y t | Duplicate current tab |
r | Reload tab |
/ | Search links |
i | Go to the first edit box |
g 1 … g 9 | Go to tab 1…9 |
g shift+4 | Go to the last tab |
shift + / | Toggle shortcut help |
a | opening the link below the cursor |
s | opening the link below the cursor in a new tab |
shift + r | Reader mode |
o | Open Search Bar |
Key | Command |
---|---|
control + f | Scroll up a page |
control + b | Scroll down a page |
control + j | Previous tab |
control + k | Next tab |
control + i | Insert mode (Shortcuts are disabled) |
control + d | Toggle dark mode |
control + u | Play video in a fullscreen |
control + p | Play video in a PIP (picture-in-picture) |
Vimlike supports navigating the web pages with the keyboard. When you type tab or n, Vimlike shows link tags. You can type tab / n, or shift + tab / shift + n to move the focus to the next link or the previous link. And you press enter to open the link. Otherwise, type the tag code or number to open the link directly.
Shortcut / opens the console to type the text and find matching links. You can also type tab / n, or shift + tab / shift + n to move the focus to the next matching link or the previous matching link.
Vimlike supports customizing the mapping of the keyboard shortcuts in Vimlike app. Please refer Customization Guide for details.
settings.map(key, COMMAND);
settings.map(key, COMMAND, {includes: url}); // mapping the key only for the url
settings.map(key, COMMAND, {excludes: url}); // mapping the key excluding the url
The parameter url can be array of strings and regular expressions. For example,
settings.map('space', VLCommand.HALF_PAGE_DOWN, {excludes: ['https://www.youtube.com/watch', 'https://www.netflix.com/watch/']});
settings.map('space', VLCommand.HALF_PAGE_DOWN, {includes: [/www\.google\.co.*\/search\?/g]});
Following script is the default key mapping.
// Scroll
settings.map('j', VLCommand.SCROLL_DOWN);
settings.map('k', VLCommand.SCROLL_UP);
settings.map('h', VLCommand.SCROLL_LEFT);
settings.map('l', VLCommand.SCROLL_RIGHT);
settings.map('d', VLCommand.HALF_PAGE_DOWN);
settings.map('u', VLCommand.HALF_PAGE_UP);
settings.map('space', VLCommand.HALF_PAGE_DOWN, {excludes: ['https://www.youtube.com/watch', 'https://www.netflix.com/watch/']});
settings.map('shift+space', VLCommand.HALF_PAGE_UP);
settings.map('ctrl+f', VLCommand.PAGE_DOWN);
settings.map('ctrl+b', VLCommand.PAGE_UP);
//settings.map('g', VLCommand.SCROLL_TO_TOP); // deprecated
settings.map('g g', VLCommand.SCROLL_TO_TOP);
settings.map('shift+g', VLCommand.SCROLL_TO_BOTTOM);
// Normal mode
settings.map('f', VLCommand.ACTIVATE_LINK);
settings.map('shift+f', VLCommand.ACTIVATE_LINK_WITH_NEW_TAB);
settings.map('n', VLCommand.NEXT_LINK);
settings.map('shift+n', VLCommand.PREV_LINK);
settings.map('tab', VLCommand.NEXT_LINK);
settings.map('shift+tab', VLCommand.PREV_LINK);
settings.map('shift+h', VLCommand.GO_BACK);
settings.map('shift+l', VLCommand.GO_FORWARD);
settings.map('q', VLCommand.PREV_TAB);
settings.map('w', VLCommand.NEXT_TAB);
settings.map('ctrl+j', VLCommand.PREV_TAB);
settings.map('g shift+t', VLCommand.PREV_TAB);
settings.map('ctrl+k', VLCommand.NEXT_TAB);
settings.map('g t', VLCommand.NEXT_TAB);
settings.map('t', VLCommand.NEW_TAB);
settings.map('i', VLCommand.FOCUS_INPUT);
settings.map('slash', VLCommand.SHOW_CONSOLE);
settings.map('ctrl+i', VLCommand.INSERT_MODE);
settings.map('g 1', VLCommand.OPEN_TAB1);
settings.map('g 2', VLCommand.OPEN_TAB2);
settings.map('g 3', VLCommand.OPEN_TAB3);
settings.map('g 4', VLCommand.OPEN_TAB4);
settings.map('g 5', VLCommand.OPEN_TAB5);
settings.map('g 6', VLCommand.OPEN_TAB6);
settings.map('g 7', VLCommand.OPEN_TAB7);
settings.map('g 8', VLCommand.OPEN_TAB8);
settings.map('g 9', VLCommand.OPEN_TAB9);
settings.map('g shift+4', VLCommand.OPEN_LAST_TAB);
settings.map('g 0', VLCommand.OPEN_TAB1);
settings.map('x', VLCommand.CLOSE_TAB);
settings.map('ctrl+u', VLCommand.VIDEO_FULLSCREEN);
settings.map('ctrl+p', VLCommand.VIDEO_PIP);
settings.map('ctrl+d', VLCommand.DARK_MODE);
settings.map('r', VLCommand.RELOAD);
settings.map('?', VLCommand.TOGGLE_HELP);
settings.map('shift+slash', VLCommand.TOGGLE_HELP);
settings.map('/', VLCommand.SHOW_CONSOLE);
settings.map('y y', VLCommand.COPY_CURRENT_URL);
settings.map('y t', VLCommand.DUPLICATE_TAB);
ID | Command |
---|---|
VLCommand.SCROLL_UP | Scroll up |
VLCommand.SCROLL_DOWN | Scroll down |
VLCommand.SCROLL_LEFT | Scroll left |
VLCommand.SCROLL_RIGHT | Scroll right |
VLCommand.HALF_PAGE_DOWN | Half page up |
VLCommand.HALF_PAGE_UP | Half page down |
VLCommand.SCROLL_TO_TOP | Go to top of page |
VLCommand.SCROLL_TO_BOTTOM | Go to bottom of page |
VLCommand.PAGE_UP | Scroll up a page |
VLCommand.PAGE_DOWN | Scroll down a page |
VLCommand.FOCUS_INPUT | Go to the first edit box |
VLCommand.NORMAL_MODE | Normal mode |
VLCommand.INSERT_MODE | Insert mode (shortcuts are disabled) |
VLCommand.TOGGLE_HELP | Toggle shortcut help |
VLCommand.DARK_MODE | Toggle dark mode |
VLCommand.GO_BACK | Go back |
VLCommand.GO_FORWARD | Go forward |
VLCommand.ACTIVATE_LINK | Toggle links |
VLCommand.ACTIVATE_LINK_WITH_NEW_TAB | Activate link hints (open in a new tab) |
VLCommand.NEXT_LINK | Move to next link |
VLCommand.PREV_LINK | Move to previous link |
VLCommand.SHOW_CONSOLE | Search links |
VLCommand.OPEN_SELECTED_LINK | Open link |
VLCommand.OPEN_SELECTED_LINK_IN_NEW_TAB | Open link in a new tab |
VLCommand.NEW_TAB | New tab |
VLCommand.PREV_TAB | Previous tab |
VLCommand.NEXT_TAB | Next tab |
VLCommand.CLOSE_TAB | Close tab |
VLCommand.OPEN_TAB1 | Open tab 1 |
VLCommand.OPEN_TAB2 | Open tab 2 |
VLCommand.OPEN_TAB3 | Open tab 3 |
VLCommand.OPEN_TAB4 | Open tab 4 |
VLCommand.OPEN_TAB5 | Open tab 5 |
VLCommand.OPEN_TAB6 | Open tab 6 |
VLCommand.OPEN_TAB7 | Open tab 7 |
VLCommand.OPEN_TAB8 | Open tab 8 |
VLCommand.OPEN_TAB9 | Open tab 9 |
VLCommand.VIDEO_FULLSCREEN | Play video in a fullscreen |
VLCommand.VIDEO_PIP | Play video in a PIP (picture-in-picture) |
VLCommand.RELOAD | Reload |
VLCommand.COPY_CURRENT_URL | Copy current URL to the clipboard |
VLCommand.DUPLICATE_TAB | Duplicate current tab |
Vimlike uses Javascript KeyboardEvent.code for the keyboard command. Following table shows codes for the special keys.
Key | Code |
---|---|
tab | Tab |
space | Space |
page up | PageUp |
page down | PageDown |
end | End |
home | Home |
← | ArrowLeft |
↑ | ArrowUp |
→ | ArrowRight |
↓ | ArrowDown |
print screen | PrintScreen |
insert | Insert |
delete | Delete |
; | Semicolon |
= | Equal |
, | Comma |
- | Minus |
. | Period |
/ | Slash |
` | Backquote |
[ | BracketLeft |
\ | Backslash |
] | BracketRight |
' | Quote |
KeyboardEvent.code uses the key based on the QWERTY layout. Therefore, key bindings of some of the latin keyboards are different. For example, the code “z” represents for the Z key on a QWERTY layout keyboard, but the same code value also represents the Y key on German keyboards.
Vimlike version 1.3.0 or above supports custom key binding and custom script for a specific website.
settings.map('bracketleft', function(vimlike) {
vimlike.click('//a[@id="pnprev"]');
});
settings.map('bracketright', function(vimlike) {
vimlike.click('//a[@id="pnnext"]');
});
// www.youtube.com
let watchUrls = ['https://www.youtube.com/watch', 'https://m.youtube.com/watch'];
// Go to Home
settings.map('g h', function(vimlike) {
window.open("https://www.youtube.com", "_self");
});
// Go to Explorer
settings.map('g e', function(vimlike) {
window.open("https://www.youtube.com/feed/explore", "_self");
});
// Go to Shorts
settings.map('g o', function(vimlike) {
window.open("https://www.youtube.com/shorts", "_self");
});
// Go to Subscription
settings.map('g s', function(vimlike) {
window.open("https://www.youtube.com/feed/subscriptions", "_self");
});
// Next track
settings.map('bracketright', function(vimlike) {
let button = document.querySelector('a.ytp-next-button');
if (button != null) {
button.click();
}
}, {includes: watchUrls});
// Skip ad
settings.map('s', function(vimlike) {
if (document.querySelector('.ad-showing')) {
const video = document.querySelector('video');
if (video && video.duration) {
video.currentTime = video.duration;
}
}
const skip = document.querySelector('button.ytp-ad-skip-button');
if (skip) {
skip.click();
}
const close = document.querySelector('button.ytp-ad-overlay-close-button');
if (close) {
close.click();
}
}, {includes: watchUrls});
settings.unmap('space');
// Go to Home
settings.map('g h', function(vimlike) {
window.open("https://www.instagram.com", "_self");
});
// Go to Inbox
settings.map('g i', function(vimlike) {
window.open("https://www.instagram.com/direct/inbox/", "_self");
});
// Go to Explorer
settings.map('g e', function(vimlike) {
window.open("https://www.instagram.com/explore/", "_self");
});
// Close
settings.map('backslash', function(vimlike) {
vimlike.click('//div[div/*[local-name()="svg" and @aria-label="Close"]]');
});
// Next
settings.map('bracketright', function(vimlike) {
vimlike.click('//button[div/span/*[local-name()="svg" and @aria-label="Next"]]');
});
// Prev
settings.map('bracketleft', function(vimlike) {
vimlike.click('//button[div/span/*[local-name()="svg" and @aria-label="Go back"]]');
});
// Go to next page
settings.map('bracketright', function(vimlike) {
vimlike.click('//a[contains(@aria-label, "Go to next page")]');
});
// Go to previous page
settings.map('bracketleft', function(vimlike) {
vimlike.click('//a[contains(@aria-label, "Go to previous page")]');
});