Handling Keyboard Input
Learn how to detect and respond to keyboard input events in your SDL3-based applications. This lesson covers key events, key codes, and modifier keys.
In this lesson, we will cover how we can detect and react to the user providing input to our application using their keyboard.
Similar to other forms of input, when SDL detects keyboard interaction, it pushes events into the event queue. We can capture these events through our event loop and handle them as needed.
Starting Point
This lesson builds on our earlier work. If you want to follow along, a minimalist project containing a Window
class that initializes SDL and creates a window, and a main
function with a basic application loop is available below.
Files
Our SDL_PollEvent(&Event)
statement will update our Event
object with any keyboard action that the user performs. We'll then detect and react to those actions within the body of the loop.
The two most common event types for handling the keyboard are SDL_EVENT_KEY_DOWN
, which is created when a button is pressed, and SDL_EVENT_KEY_UP
, for when a button is released.
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_EVENT_KEY_DOWN) {
// ...
} else if (Event.type == SDL_EVENT_KEY_UP) {
// ...
}
}
To find out which button was pressed or released, we need to check the key code. If we have an SDL_Event
object named Event
, and its type
is SDL_EVENT_KEY_UP
or SDL_EVENT_KEY_DOWN
, we can access the key code using Event.key.key
:
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_EVENT_KEY_DOWN) {
std::cout << "Key: " << Event.key.key;
}
}
Key: 32
Key codes are basic integers, but SDL provides variables to help us understand which keys these integers represent. These variables are identified by the SDLK_
prefix. For example, the integer that represents the spacebar is available as SDLK_SPACE
.
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_EVENT_KEY_DOWN) {
if (Event.key.key == SDLK_SPACE) {
std::cout << "Spacebar Pressed";
}
}
}
Spacebar Pressed
In this example, we react to the arrow keys being pressed:
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_EVENT_KEY_DOWN) {
if (Event.key.key == SDLK_UP) {
// Up Arrow
} else if (Event.key.key == SDLK_DOWN) {
// Down Arrow
} else if (Event.key.key == SDLK_LEFT) {
// Left Arrow
} else if (Event.key.key == SDLK_RIGHT) {
// Right Arrow
}
}
}
A list of all the key codes is available in the SDL_keycode.h
header file, documented here.
Using SDL_KeyboardEvent
When our SDL_Event
object has a type
of SDL_EVENT_KEY_DOWN
or SDL_EVENT_KEY_UP
, most of the information we care about is within the key
struct of that event. This struct has a type of SDL_KeyboardEvent
:
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_EVENT_KEY_DOWN ||
Event.type == SDL_EVENT_KEY_UP
) {
SDL_KeyboardEvent KeyEvent = Event.key;
}
}
Our main event loop can get long and complex, so we want to proactively mitigate this growth in our design.
One of the main ways of doing this is restricting our main event loop to just determining the high-level nature of each event (typically by examining the type
variable) and then handing it off to a function to implement the required reaction.
A handler for keyboard events will be most interested in the SDL_KeyboardEvent
subobject stored in the key
variable, so that tends to be what we provide from our main loop:
// Event Loop
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_EVENT_KEY_DOWN ||
Event.type == SDL_EVENT_KEY_UP
) {
HandleKeyboard(Event.key);
}
}
// Handler
void HandleKeyboard(const SDL_KeyboardEvent& E) {
if (E.key == SDLK_UP) {
// Up Arrow
} else if (E.key == SDLK_DOWN) {
// Down Arrow
} else if (E.key == SDLK_LEFT) {
// Left Arrow
} else if (E.key == SDLK_RIGHT) {
// Right Arrow
}
}
To understand whether the key was pressed or released, we can access the down
boolean of the SDL_KeyboardEvent
. This is true
for a key press and false
for a release:
void HandleKeyboard(const SDL_KeyboardEvent& E) {
if (E.down) {
// Key Pressed
} else {
// Key Released
}
}
Converting Key Codes to Key Names
If we have a keycode, and we want to understand what key was pressed, the SDL_GetKeyName()
function can help us. It converts a key code to a more understandable name:
void HandleKeyboard(const SDL_KeyboardEvent& E) {
std::cout
<< "Key Pressed! Key Code: "
<< E.key
<< ", Key Name: "
<< SDL_GetKeyName(E.key)
<< '\n';
}
Key Pressed! Key Code: 113, Key Name: Q
Key Pressed! Key Code: 119, Key Name: W
Key Pressed! Key Code: 101, Key Name: E
Key Pressed! Key Code: 114, Key Name: R
Key Pressed! Key Code: 116, Key Name: T
Key Pressed! Key Code: 121, Key Name: Y
Modifiers - Ctrl, Alt, Shift, etc
A small subset of keys on our keyboard are considered modifier keys as, by convention, applications use them to modify the intent of other inputs. For example, in word processing applications, pressing a letter key whilst the shift key is held down is interpreted as requesting the uppercase form of that letter.
We can detect keydown and keyup events for these keys like any other:
void HandleKeyboard(const SDL_KeyboardEvent& E) {
if (E.down) {
std::cout << SDL_GetKeyName(E.key)
<< " pressed\n";
} else {
std::cout << SDL_GetKeyName(E.key)
<< " released\n";
}
}
Left Ctrl pressed
Left Ctrl released
Left Shift pressed
Left Shift released
However, for convenience, the SDL_KeyboardEvent
struct also has a mod
member variable, which tells us which (if any) modifier keys were active during any keyboard event.
This variable is an integer that acts as a bit set, so we use the bitwise operators to understand which modifiers were active. You can learn more about this technique in our .
SDL provides constants to use with this bit set. For example, we can use SDL_KMOD_LSHIFT
as the right operand to the &
operator, which will return true
if the left shift button was active.
Below, we detect presses of the space bar, with different behaviors depending on whether or not left shift was active:
void HandleKeyboard(const SDL_KeyboardEvent& E) {
if (E.down && E.key == SDLK_SPACE) {
std::cout << "Space bar was pressed\n";
if (E.mod & SDL_KMOD_LSHIFT) {
std::cout << " with left shift active\n";
}
}
}
Space bar was pressed
Space bar was pressed
with left shift active
We can build more elaborate logic by combining boolean or bitwise operators. Below, we use the boolean &&
operator to determine if multiple modifiers are simultaneously active, and the bitwise |
operator to detect if any of a range of modifiers are active:
void HandleKeyboard(const SDL_KeyboardEvent& E) {
if (E.down && E.key == SDLK_SPACE) {
std::cout << "Space bar was pressed\n";
if ((E.mod & SDL_KMOD_LSHIFT)
&& (E.mod & SDL_KMOD_LCTRL)) {
std::cout << " with left shift AND "
"left ctrl active\n";
} else if (E.mod &
(SDL_KMOD_LSHIFT | SDL_KMOD_LCTRL)) {
std::cout << " with left shift OR "
"left ctrl active\n";
}
}
}
Space bar was pressed
Space bar was pressed
with left shift AND left ctrl active
Space bar was pressed
with left shift OR left ctrl active
The full list of SDL_KMOD_
constants provided by SDL are listed in the official documentation.
Key Repeats
On many platforms, holding a key down sends a continuous stream of input events after a brief pause. SDL reports these as repeated keydown events.
The following program logs both presses and releases of the spacebar but, if we hold the spacebar down, we'll get a stream of continuous key press events:
void HandleKeyboard(const SDL_KeyboardEvent& E) {
if (E.down && E.key == SDLK_SPACE) {
std::cout << "Space bar was pressed\n";
} else if (!E.down && E.key == SDLK_SPACE) {
std::cout << "Space bar was released\n";
}
}
Space bar was pressed
Space bar was pressed
Space bar was pressed
Space bar was pressed
Space bar was released
We can distinguish between initial and repeat events by inspecting the repeat
variable of the SDL_KeyboardEvent
(or the key.repeat
variable of the SDL_Event
).
The repeat
value will be false
for the initial keydown, and true
if it was caused by the user continuing to hold the key down:
void HandleKeyboard(const SDL_KeyboardEvent& E) {
if (E.down && E.key == SDLK_SPACE) {
std::cout << "\nSpace bar was pressed - "
<< (E.repeat ? "Repeat" : "Initial");
} else if (!E.down && E.key == SDLK_SPACE) {
std::cout << "Space bar was released\n";
}
}
Space bar was pressed - Initial
Space bar was pressed - Repeat
Space bar was pressed - Repeat
Space bar was pressed - Repeat
Space bar was released
Key Codes and Scan Codes
Keyboards can have different layouts. Much of the world uses the QWERTY layout, but others, such as AZERTY, are also common.
This variation means there are two different ways to represent keys.
We've been using key codes in this lesson, sometimes also referred to as virtual key codes. These codes are the simplest to understand. If the user presses the button labeled "Q" on their keyboard, a key code representing Q will be reported. This assumes the user configured their system correctly - that is, the layout they selected in their system settings matches the layout of their physical keyboard.
Scan codes, sometimes referred to as physical key codes, ignore the configured layout and instead represent which physical button was pressed. In SDL3, the scan code is reported based on a standard hardware layout.
For example, if the user presses the button that would be labeled "Q" on a standard US-QWERTY keyboard, a scan code representing Q will be reported.
This means that if the user is on an AZERTY keyboard, for example, and they press the button labeled "A", the scan code we receive will be for "Q", as that physical key is in the "Q" position on a standard US-QWERTY keyboard:

Scan codes are primarily useful when we care about the position of the buttons, rather than their alphabetic representations. For example, the W, A, S, and D keys are commonly used for movement in video games, due to their position and proximity to each other on a QWERTY keyboard.
On an AZERTY layout, those same physical buttons are labeled Z, Q, S, and D. But, if we're using scan codes, we don't need to care about that difference. On such a keyboard, Z will be reported as the W scan code. So, if our movement manager interprets input using the button' scan codes, players can use the same physical controls to move their character, regardless of how those buttons are labelled on their specific keyboard.
We'd take the opposite approach if the user is currently typing something into our program, such as interacting with a chat box. In that scenario, if they press the button labeled "Z" on their AZERTY keyboard, we assume they do indeed want to type the letter Z. So, our chat box component should interpret inputs using the key code (which would be Z) rather than the scan code (which would be W).
Within an SDL_KeyboardEvent
, the key code is available as the key
member, while the scan code is available as scancode
.
void HandleKeyboard(const SDL_KeyboardEvent& E) {
SDL_Keycode KeyCode{E.key};
SDL_Scancode ScanCode{E.scancode};
}
The specific scancodes used are defined in the Human Interface Devices (HID) component of the USB standard, if you want a lower level understanding of keyboard interfaces.
Summary
This lesson introduced how to handle keyboard input using SDL3. The key takeaways include:
- Detecting and handling keyboard events with SDL.
- Using
SDL_EVENT_KEY_DOWN
andSDL_EVENT_KEY_UP
events. - Understanding key codes and scan codes.
- Utilizing the
SDL_GetKeyName()
function. - Handling modifier keys such as Ctrl, Alt, and Shift.
- Distinguishing between initial and repeated key events.
Managing Window Input Focus
Learn how to manage and control window input focus in SDL3 applications, including how to create, detect, and manipulate window focus states.