diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp
index 08a05c558a..8ecc1b202a 100644
--- a/src/citra/citra.cpp
+++ b/src/citra/citra.cpp
@@ -35,6 +35,7 @@
 #include "common/string_util.h"
 #include "core/core.h"
 #include "core/file_sys/cia_container.h"
+#include "core/frontend/applets/default_applets.h"
 #include "core/gdbstub/gdbstub.h"
 #include "core/hle/service/am/am.h"
 #include "core/loader/loader.h"
@@ -271,6 +272,9 @@ int main(int argc, char** argv) {
     Settings::values.movie_record = std::move(movie_record);
     Settings::Apply();
 
+    // Register frontend applets
+    Frontend::RegisterDefaultApplets();
+
     std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen)};
 
     Core::System& system{Core::System::GetInstance()};
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 7b0d0eaac9..6c1760d2c8 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -7,6 +7,8 @@ add_executable(citra-qt
     Info.plist
     aboutdialog.cpp
     aboutdialog.h
+    applets/swkbd.cpp
+    applets/swkbd.h
     bootmanager.cpp
     bootmanager.h
     camera/camera_util.cpp
diff --git a/src/citra_qt/applets/swkbd.cpp b/src/citra_qt/applets/swkbd.cpp
new file mode 100644
index 0000000000..a9237389a6
--- /dev/null
+++ b/src/citra_qt/applets/swkbd.cpp
@@ -0,0 +1,129 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QDialogButtonBox>
+#include <QLabel>
+#include <QLineEdit>
+#include <QMessageBox>
+#include <QString>
+#include <QVBoxLayout>
+#include "citra_qt/applets/swkbd.h"
+
+QtKeyboardValidator::QtKeyboardValidator(QtKeyboard* keyboard_) : keyboard(keyboard_) {}
+
+QtKeyboardValidator::State QtKeyboardValidator::validate(QString& input, int& pos) const {
+    if (keyboard->ValidateFilters(input.toStdString()) == Frontend::ValidationError::None) {
+        if (input.size() > keyboard->config.max_text_length)
+            return State::Invalid;
+        return State::Acceptable;
+    } else {
+        return State::Invalid;
+    }
+}
+
+QtKeyboardDialog::QtKeyboardDialog(QWidget* parent, QtKeyboard* keyboard_)
+    : QDialog(parent), keyboard(keyboard_) {
+    using namespace Frontend;
+    KeyboardConfig config = keyboard->config;
+    layout = new QVBoxLayout;
+    label = new QLabel(QString::fromStdString(config.hint_text));
+    line_edit = new QLineEdit;
+    line_edit->setValidator(new QtKeyboardValidator(keyboard));
+    buttons = new QDialogButtonBox;
+    // Initialize buttons
+    switch (config.button_config) {
+    case ButtonConfig::Triple:
+        buttons->addButton(config.has_custom_button_text
+                               ? QString::fromStdString(config.button_text[2])
+                               : tr(BUTTON_OKAY),
+                           QDialogButtonBox::ButtonRole::AcceptRole);
+        buttons->addButton(config.has_custom_button_text
+                               ? QString::fromStdString(config.button_text[1])
+                               : tr(BUTTON_FORGOT),
+                           QDialogButtonBox::ButtonRole::HelpRole);
+        buttons->addButton(config.has_custom_button_text
+                               ? QString::fromStdString(config.button_text[0])
+                               : tr(BUTTON_CANCEL),
+                           QDialogButtonBox::ButtonRole::RejectRole);
+        break;
+    case ButtonConfig::Dual:
+        buttons->addButton(config.has_custom_button_text
+                               ? QString::fromStdString(config.button_text[1])
+                               : tr(BUTTON_OKAY),
+                           QDialogButtonBox::ButtonRole::AcceptRole);
+        buttons->addButton(config.has_custom_button_text
+                               ? QString::fromStdString(config.button_text[0])
+                               : tr(BUTTON_CANCEL),
+                           QDialogButtonBox::ButtonRole::RejectRole);
+        break;
+    case ButtonConfig::Single:
+        buttons->addButton(config.has_custom_button_text
+                               ? QString::fromStdString(config.button_text[0])
+                               : tr(BUTTON_OKAY),
+                           QDialogButtonBox::ButtonRole::AcceptRole);
+        break;
+    case ButtonConfig::None:
+        break;
+    }
+    connect(buttons, &QDialogButtonBox::accepted, this, [=] { Submit(); });
+    connect(buttons, &QDialogButtonBox::rejected, this, [=] {
+        button = QtKeyboard::cancel_id;
+        accept();
+    });
+    connect(buttons, &QDialogButtonBox::helpRequested, this, [=] {
+        button = QtKeyboard::forgot_id;
+        accept();
+    });
+    layout->addWidget(label);
+    layout->addWidget(line_edit);
+    layout->addWidget(buttons);
+    setLayout(layout);
+}
+
+void QtKeyboardDialog::Submit() {
+    auto error = keyboard->ValidateInput(line_edit->text().toStdString());
+    if (error != Frontend::ValidationError::None) {
+        HandleValidationError(error);
+    } else {
+        button = keyboard->ok_id;
+        text = line_edit->text();
+        accept();
+    }
+}
+
+void QtKeyboardDialog::HandleValidationError(Frontend::ValidationError error) {
+    using namespace Frontend;
+    const std::unordered_map<ValidationError, QString> VALIDATION_ERROR_MESSAGES = {
+        {ValidationError::FixedLengthRequired,
+         tr("Text length is not correct (should be %1 characters)")
+             .arg(keyboard->config.max_text_length)},
+        {ValidationError::MaxLengthExceeded,
+         tr("Text is too long (should be no more than %1 characters)")
+             .arg(keyboard->config.max_text_length)},
+        {ValidationError::BlankInputNotAllowed, tr("Blank input is not allowed")},
+        {ValidationError::EmptyInputNotAllowed, tr("Empty input is not allowed")},
+    };
+    QMessageBox::critical(this, tr("Validation error"), VALIDATION_ERROR_MESSAGES.at(error));
+}
+
+QtKeyboard::QtKeyboard(QWidget& parent_) : parent(parent_) {}
+
+void QtKeyboard::Setup(const Frontend::KeyboardConfig* config) {
+    SoftwareKeyboard::Setup(config);
+    if (this->config.button_config != Frontend::ButtonConfig::None) {
+        ok_id = static_cast<u8>(this->config.button_config);
+    }
+    QMetaObject::invokeMethod(this, "OpenInputDialog", Qt::BlockingQueuedConnection);
+}
+
+void QtKeyboard::OpenInputDialog() {
+    QtKeyboardDialog dialog(&parent, this);
+    dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
+                          Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
+    dialog.setWindowModality(Qt::WindowModal);
+    dialog.exec();
+    LOG_INFO(Frontend, "SWKBD input dialog finished, text={}, button={}", dialog.text.toStdString(),
+             dialog.button);
+    Finalize(dialog.text.toStdString(), dialog.button);
+}
diff --git a/src/citra_qt/applets/swkbd.h b/src/citra_qt/applets/swkbd.h
new file mode 100644
index 0000000000..cfcbcb45e7
--- /dev/null
+++ b/src/citra_qt/applets/swkbd.h
@@ -0,0 +1,65 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QDialog>
+#include <QValidator>
+#include "core/frontend/applets/swkbd.h"
+
+class QDialogButtonBox;
+class QLabel;
+class QLineEdit;
+class QVBoxLayout;
+class QtKeyboard;
+
+class QtKeyboardValidator final : public QValidator {
+public:
+    explicit QtKeyboardValidator(QtKeyboard* keyboard);
+    State validate(QString& input, int& pos) const override;
+
+private:
+    QtKeyboard* keyboard;
+};
+
+class QtKeyboardDialog final : public QDialog {
+    Q_OBJECT
+
+public:
+    QtKeyboardDialog(QWidget* parent, QtKeyboard* keyboard);
+    void Submit();
+
+private:
+    void HandleValidationError(Frontend::ValidationError error);
+    QDialogButtonBox* buttons;
+    QLabel* label;
+    QLineEdit* line_edit;
+    QVBoxLayout* layout;
+    QtKeyboard* keyboard;
+    QString text;
+    u8 button;
+
+    friend class QtKeyboard;
+};
+
+class QtKeyboard final : public QObject, public Frontend::SoftwareKeyboard {
+    Q_OBJECT
+
+public:
+    explicit QtKeyboard(QWidget& parent);
+    void Setup(const Frontend::KeyboardConfig* config) override;
+
+private:
+    Q_INVOKABLE void OpenInputDialog();
+
+    /// Index of the buttons
+    u8 ok_id;
+    static constexpr u8 forgot_id = 1;
+    static constexpr u8 cancel_id = 0;
+
+    QWidget& parent;
+
+    friend class QtKeyboardDialog;
+    friend class QtKeyboardValidator;
+};
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index e91b6a1b85..0ff04e7f61 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -16,6 +16,7 @@
 #include <QtGui>
 #include <QtWidgets>
 #include "citra_qt/aboutdialog.h"
+#include "citra_qt/applets/swkbd.h"
 #include "citra_qt/bootmanager.h"
 #include "citra_qt/camera/qt_multimedia_camera.h"
 #include "citra_qt/camera/still_image_camera.h"
@@ -50,6 +51,7 @@
 #include "common/string_util.h"
 #include "core/core.h"
 #include "core/file_sys/archive_source_sd_savedata.h"
+#include "core/frontend/applets/default_applets.h"
 #include "core/gdbstub/gdbstub.h"
 #include "core/hle/service/fs/archive.h"
 #include "core/loader/loader.h"
@@ -1468,6 +1470,10 @@ int main(int argc, char* argv[]) {
     Camera::RegisterFactory("qt", std::make_unique<Camera::QtMultimediaCameraFactory>());
     Camera::QtMultimediaCameraHandler::Init();
 
+    // Register frontend applets
+    Frontend::RegisterDefaultApplets();
+    Frontend::RegisterSoftwareKeyboard(std::make_shared<QtKeyboard>(main_window));
+
     main_window.show();
     return app.exec();
 }
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 6f831b359e..0166beff0f 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -158,6 +158,8 @@ void FileBackend::Write(const Entry& entry) {
     SUB(Debug, GDBStub)                                                                            \
     CLS(Kernel)                                                                                    \
     SUB(Kernel, SVC)                                                                               \
+    CLS(Applet)                                                                                    \
+    SUB(Applet, SWKBD)                                                                             \
     CLS(Service)                                                                                   \
     SUB(Service, SRV)                                                                              \
     SUB(Service, FRD)                                                                              \
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 31ba5beae8..a51399dee1 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -48,6 +48,9 @@ enum class Class : ClassType {
     Debug_GDBStub,     ///< GDB Stub
     Kernel,            ///< The HLE implementation of the CTR kernel
     Kernel_SVC,        ///< Kernel system calls
+    Applet,            ///< HLE implementation of system applets. Each applet
+                       ///  should have its own subclass.
+    Applet_SWKBD,      ///< The Software Keyboard applet
     Service,           ///< HLE implementation of system services. Each major service
                        ///  should have its own subclass.
     Service_SRV,       ///< The SRV (Service Directory) implementation
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 7e889829ad..35092d9998 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -68,6 +68,10 @@ add_library(core STATIC
     file_sys/savedata_archive.h
     file_sys/title_metadata.cpp
     file_sys/title_metadata.h
+    frontend/applets/default_applets.cpp
+    frontend/applets/default_applets.h
+    frontend/applets/swkbd.cpp
+    frontend/applets/swkbd.h
     frontend/camera/blank_camera.cpp
     frontend/camera/blank_camera.h
     frontend/camera/factory.cpp
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 6d3ddc1369..013ef4c2e5 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -199,6 +199,10 @@ const Service::SM::ServiceManager& System::ServiceManager() const {
     return *service_manager;
 }
 
+void System::RegisterSoftwareKeyboard(std::shared_ptr<Frontend::SoftwareKeyboard> swkbd) {
+    registered_swkbd = std::move(swkbd);
+}
+
 void System::Shutdown() {
     // Log last frame performance stats
     auto perf_results = GetAndResetPerfStats();
diff --git a/src/core/core.h b/src/core/core.h
index a52a017087..036179149f 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -7,6 +7,7 @@
 #include <memory>
 #include <string>
 #include "common/common_types.h"
+#include "core/frontend/applets/swkbd.h"
 #include "core/loader/loader.h"
 #include "core/memory.h"
 #include "core/perf_stats.h"
@@ -150,6 +151,14 @@ public:
         return *app_loader;
     }
 
+    /// Frontend Applets
+
+    void RegisterSoftwareKeyboard(std::shared_ptr<Frontend::SoftwareKeyboard> swkbd);
+
+    std::shared_ptr<Frontend::SoftwareKeyboard> GetSoftwareKeyboard() const {
+        return registered_swkbd;
+    }
+
 private:
     /**
      * Initialize the emulated system.
@@ -180,6 +189,9 @@ private:
     /// Service manager
     std::shared_ptr<Service::SM::ServiceManager> service_manager;
 
+    /// Frontend applets
+    std::shared_ptr<Frontend::SoftwareKeyboard> registered_swkbd;
+
     static System s_instance;
 
     ResultStatus status = ResultStatus::Success;
diff --git a/src/core/frontend/applets/default_applets.cpp b/src/core/frontend/applets/default_applets.cpp
new file mode 100644
index 0000000000..814b6381e0
--- /dev/null
+++ b/src/core/frontend/applets/default_applets.cpp
@@ -0,0 +1,12 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/frontend/applets/default_applets.h"
+#include "core/frontend/applets/swkbd.h"
+
+namespace Frontend {
+void RegisterDefaultApplets() {
+    RegisterSoftwareKeyboard(std::make_shared<DefaultKeyboard>());
+}
+} // namespace Frontend
diff --git a/src/core/frontend/applets/default_applets.h b/src/core/frontend/applets/default_applets.h
new file mode 100644
index 0000000000..d537b7bf01
--- /dev/null
+++ b/src/core/frontend/applets/default_applets.h
@@ -0,0 +1,13 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+namespace Frontend {
+/**
+ * Registers default, frontend-independent applet implementations.
+ * Will be replaced later if any frontend-specific implementation is available.
+ */
+void RegisterDefaultApplets();
+} // namespace Frontend
diff --git a/src/core/frontend/applets/swkbd.cpp b/src/core/frontend/applets/swkbd.cpp
new file mode 100644
index 0000000000..335d99d167
--- /dev/null
+++ b/src/core/frontend/applets/swkbd.cpp
@@ -0,0 +1,162 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cctype>
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/frontend/applets/swkbd.h"
+#include "core/hle/service/cfg/cfg.h"
+
+namespace Frontend {
+
+ValidationError SoftwareKeyboard::ValidateFilters(const std::string& input) const {
+    if (config.filters.prevent_digit) {
+        if (std::any_of(input.begin(), input.end(),
+                        [](unsigned char c) { return std::isdigit(c); })) {
+            return ValidationError::DigitNotAllowed;
+        }
+    }
+    if (config.filters.prevent_at) {
+        if (input.find('@') != std::string::npos) {
+            return ValidationError::AtSignNotAllowed;
+        }
+    }
+    if (config.filters.prevent_percent) {
+        if (input.find('%') != std::string::npos) {
+            return ValidationError::PercentNotAllowed;
+        }
+    }
+    if (config.filters.prevent_backslash) {
+        if (input.find('\\') != std::string::npos) {
+            return ValidationError::BackslashNotAllowed;
+        }
+    }
+    if (config.filters.prevent_profanity) {
+        // TODO: check the profanity filter
+        LOG_INFO(Frontend, "App requested swkbd profanity filter, but its not implemented.");
+    }
+    if (config.filters.enable_callback) {
+        // TODO: check the callback
+        LOG_INFO(Frontend, "App requested a swkbd callback, but its not implemented.");
+    }
+    return ValidationError::None;
+}
+
+ValidationError SoftwareKeyboard::ValidateInput(const std::string& input) const {
+    ValidationError error;
+    if ((error = ValidateFilters(input)) != ValidationError::None) {
+        return error;
+    }
+
+    // TODO(jroweboy): Is max_text_length inclusive or exclusive?
+    if (input.size() > config.max_text_length) {
+        return ValidationError::MaxLengthExceeded;
+    }
+
+    bool is_blank =
+        std::all_of(input.begin(), input.end(), [](unsigned char c) { return std::isspace(c); });
+    bool is_empty = input.empty();
+    switch (config.accept_mode) {
+    case AcceptedInput::FixedLength:
+        if (input.size() != config.max_text_length) {
+            return ValidationError::FixedLengthRequired;
+        }
+        break;
+    case AcceptedInput::NotEmptyAndNotBlank:
+        if (is_blank) {
+            return ValidationError::BlankInputNotAllowed;
+        }
+        if (is_empty) {
+            return ValidationError::EmptyInputNotAllowed;
+        }
+        break;
+    case AcceptedInput::NotBlank:
+        if (is_blank) {
+            return ValidationError::BlankInputNotAllowed;
+        }
+        break;
+    case AcceptedInput::NotEmpty:
+        if (is_empty) {
+            return ValidationError::EmptyInputNotAllowed;
+        }
+        break;
+    case AcceptedInput::Anything:
+        return ValidationError::None;
+    default:
+        // TODO(jroweboy): What does hardware do in this case?
+        LOG_CRITICAL(Frontend, "Application requested unknown validation method. Method: {}",
+                     static_cast<u32>(config.accept_mode));
+        UNREACHABLE();
+    }
+
+    return ValidationError::None;
+}
+
+ValidationError SoftwareKeyboard::ValidateButton(u8 button) const {
+    switch (config.button_config) {
+    case ButtonConfig::None:
+        return ValidationError::None;
+    case ButtonConfig::Single:
+        if (button != 0) {
+            return ValidationError::ButtonOutOfRange;
+        }
+        break;
+    case ButtonConfig::Dual:
+        if (button > 1) {
+            return ValidationError::ButtonOutOfRange;
+        }
+        break;
+    case ButtonConfig::Triple:
+        if (button > 2) {
+            return ValidationError::ButtonOutOfRange;
+        }
+        break;
+    default:
+        UNREACHABLE();
+    }
+    return ValidationError::None;
+}
+
+ValidationError SoftwareKeyboard::Finalize(const std::string& text, u8 button) {
+    ValidationError error;
+    if ((error = ValidateInput(text)) != ValidationError::None) {
+        return error;
+    }
+    if ((error = ValidateButton(button)) != ValidationError::None) {
+        return error;
+    }
+    data = {text, button};
+}
+
+void DefaultKeyboard::Setup(const Frontend::KeyboardConfig* config) {
+    SoftwareKeyboard::Setup(config);
+    std::string username = Common::UTF16ToUTF8(Service::CFG::GetCurrentModule()->GetUsername());
+    switch (this->config.button_config) {
+    case ButtonConfig::None:
+    case ButtonConfig::Single:
+        Finalize(username, 0);
+        break;
+    case ButtonConfig::Dual:
+        Finalize(username, 1);
+        break;
+    case ButtonConfig::Triple:
+        Finalize(username, 2);
+        break;
+    default:
+        UNREACHABLE();
+    }
+}
+
+void RegisterSoftwareKeyboard(std::shared_ptr<SoftwareKeyboard> applet) {
+    Core::System::GetInstance().RegisterSoftwareKeyboard(applet);
+}
+
+std::shared_ptr<SoftwareKeyboard> GetRegisteredSoftwareKeyboard() {
+    return Core::System::GetInstance().GetSoftwareKeyboard();
+}
+
+} // namespace Frontend
diff --git a/src/core/frontend/applets/swkbd.h b/src/core/frontend/applets/swkbd.h
new file mode 100644
index 0000000000..5c07eb12d6
--- /dev/null
+++ b/src/core/frontend/applets/swkbd.h
@@ -0,0 +1,131 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <unordered_map>
+#include <utility>
+#include <vector>
+#include "common/assert.h"
+
+namespace Frontend {
+
+enum class AcceptedInput {
+    Anything,            /// All inputs are accepted.
+    NotEmpty,            /// Empty inputs are not accepted.
+    NotEmptyAndNotBlank, /// Empty or blank inputs (consisting solely of whitespace) are not
+                         /// accepted.
+    NotBlank,    /// Blank inputs (consisting solely of whitespace) are not accepted, but empty
+                 /// inputs are.
+    FixedLength, /// The input must have a fixed length (specified by maxTextLength in
+                 /// swkbdInit).
+};
+
+enum class ButtonConfig {
+    Single, /// Ok button
+    Dual,   /// Cancel | Ok buttons
+    Triple, /// Cancel | I Forgot | Ok buttons
+    None,   /// No button (returned by swkbdInputText in special cases)
+};
+
+/// Default English button text mappings. Frontends may need to copy this to internationalize it.
+constexpr char BUTTON_OKAY[] = "Ok";
+constexpr char BUTTON_CANCEL[] = "Cancel";
+constexpr char BUTTON_FORGOT[] = "I Forgot";
+
+/// Configuration thats relevent to frontend implementation of applets. Anything missing that we
+/// later learn is needed can be added here and filled in by the backend HLE applet
+struct KeyboardConfig {
+    ButtonConfig button_config;
+    AcceptedInput accept_mode;   /// What kinds of input are accepted (blank/empty/fixed width)
+    bool multiline_mode;         /// True if the keyboard accepts multiple lines of input
+    u16 max_text_length;         /// Maximum number of letters allowed if its a text input
+    u16 max_digits;              /// Maximum number of numbers allowed if its a number input
+    std::string hint_text;       /// Displayed in the field as a hint before
+    bool has_custom_button_text; /// If true, use the button_text instead
+    std::vector<std::string> button_text; /// Contains the button text that the caller provides
+    struct Filters {
+        bool prevent_digit;     /// Disallow the use of more than a certain number of digits
+                                /// TODO: how many is a certain number
+        bool prevent_at;        /// Disallow the use of the @ sign.
+        bool prevent_percent;   /// Disallow the use of the % sign.
+        bool prevent_backslash; /// Disallow the use of the \ sign.
+        bool prevent_profanity; /// Disallow profanity using Nintendo's profanity filter.
+        bool enable_callback;   /// Use a callback in order to check the input.
+    };
+    Filters filters;
+};
+
+struct KeyboardData {
+    std::string text;
+    u8 button{};
+};
+
+enum class ValidationError {
+    None,
+    // Button Selection
+    ButtonOutOfRange,
+    // Configured Filters
+    DigitNotAllowed,
+    AtSignNotAllowed,
+    PercentNotAllowed,
+    BackslashNotAllowed,
+    ProfanityNotAllowed,
+    CallbackFailed,
+    // Allowed Input Type
+    FixedLengthRequired,
+    MaxLengthExceeded,
+    BlankInputNotAllowed,
+    EmptyInputNotAllowed,
+};
+
+class SoftwareKeyboard {
+public:
+    virtual void Setup(const KeyboardConfig* config) {
+        this->config = KeyboardConfig(*config);
+    }
+    const KeyboardData* ReceiveData() {
+        return &data;
+    }
+
+    /**
+     * Validates if the provided string breaks any of the filter rules. This is meant to be called
+     * whenever the user input changes to check to see if the new input is valid. Frontends can
+     * decide if they want to check the input continuously or once before submission
+     */
+    ValidationError ValidateFilters(const std::string& input) const;
+
+    /**
+     * Validates the the provided string doesn't break any extra rules like "input must not be
+     * empty". This will be called by Finalize but can be called earlier if the frontend needs
+     */
+    ValidationError ValidateInput(const std::string& input) const;
+
+    /**
+     * Verifies that the selected button is valid. This should be used as the last check before
+     * closing.
+     */
+    ValidationError ValidateButton(u8 button) const;
+
+    /**
+     * Runs all validation phases. If successful, stores the data so that the HLE applet in core can
+     * send this to the calling application
+     */
+    ValidationError Finalize(const std::string& text, u8 button);
+
+protected:
+    KeyboardConfig config;
+    KeyboardData data;
+};
+
+class DefaultKeyboard final : public SoftwareKeyboard {
+public:
+    void Setup(const KeyboardConfig* config) override;
+};
+
+void RegisterSoftwareKeyboard(std::shared_ptr<SoftwareKeyboard> applet);
+
+std::shared_ptr<SoftwareKeyboard> GetRegisteredSoftwareKeyboard();
+
+} // namespace Frontend
diff --git a/src/core/hle/applets/swkbd.cpp b/src/core/hle/applets/swkbd.cpp
index 6b181de06c..e7ea84572e 100644
--- a/src/core/hle/applets/swkbd.cpp
+++ b/src/core/hle/applets/swkbd.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <algorithm>
 #include <cstring>
 #include <string>
 #include "common/assert.h"
@@ -69,22 +70,51 @@ ResultCode SoftwareKeyboard::StartImpl(Service::APT::AppletStartupParameter cons
 
     DrawScreenKeyboard();
 
+    using namespace Frontend;
+    frontend_applet = GetRegisteredSoftwareKeyboard();
+    if (frontend_applet) {
+        KeyboardConfig frontend_config = ToFrontendConfig(config);
+        frontend_applet->Setup(&frontend_config);
+    }
+
     is_running = true;
     return RESULT_SUCCESS;
 }
 
 void SoftwareKeyboard::Update() {
-    // TODO(Subv): Handle input using the touch events from the HID module
-
-    // TODO(Subv): Remove this hardcoded text
-    std::u16string text = Common::UTF8ToUTF16("Citra");
+    using namespace Frontend;
+    KeyboardData data(*frontend_applet->ReceiveData());
+    std::u16string text = Common::UTF8ToUTF16(data.text);
     memcpy(text_memory->GetPointer(), text.c_str(), text.length() * sizeof(char16_t));
+    switch (config.num_buttons_m1) {
+    case SoftwareKeyboardButtonConfig::SingleButton:
+        config.return_code = SoftwareKeyboardResult::D0Click;
+        break;
+    case SoftwareKeyboardButtonConfig::DualButton:
+        if (data.button == 0)
+            config.return_code = SoftwareKeyboardResult::D1Click0;
+        else
+            config.return_code = SoftwareKeyboardResult::D1Click1;
+        break;
+    case SoftwareKeyboardButtonConfig::TripleButton:
+        if (data.button == 0)
+            config.return_code = SoftwareKeyboardResult::D2Click0;
+        else if (data.button == 1)
+            config.return_code = SoftwareKeyboardResult::D2Click1;
+        else
+            config.return_code = SoftwareKeyboardResult::D2Click2;
+        break;
+    case SoftwareKeyboardButtonConfig::NoButton:
+        // TODO: find out what is actually returned
+        config.return_code = SoftwareKeyboardResult::None;
+        break;
+    default:
+        LOG_CRITICAL(Applet_SWKBD, "Unknown button config {}",
+                     static_cast<int>(config.num_buttons_m1));
+        UNREACHABLE();
+    }
 
-    // TODO(Subv): Ask for input and write it to the shared memory
-    // TODO(Subv): Find out what are the possible values for the return code,
-    // some games seem to check for a hardcoded 2
-    config.return_code = 2;
-    config.text_length = 6;
+    config.text_length = static_cast<u16>(text.size());
     config.text_offset = 0;
 
     // TODO(Subv): We're finalizing the applet immediately after it's started,
@@ -93,13 +123,7 @@ void SoftwareKeyboard::Update() {
 }
 
 void SoftwareKeyboard::DrawScreenKeyboard() {
-    auto bottom_screen = Service::GSP::GetFrameBufferInfo(0, 1);
-    auto info = bottom_screen->framebuffer_info[bottom_screen->index];
-
-    // TODO(Subv): Draw the HLE keyboard, for now just zero-fill the framebuffer
-    Memory::ZeroBlock(info.address_left, info.stride * 320);
-
-    Service::GSP::SetBufferSwap(1, info);
+    // TODO(Subv): Draw the HLE keyboard, for now just do nothing
 }
 
 void SoftwareKeyboard::Finalize() {
@@ -114,5 +138,44 @@ void SoftwareKeyboard::Finalize() {
 
     is_running = false;
 }
+
+Frontend::KeyboardConfig SoftwareKeyboard::ToFrontendConfig(
+    const SoftwareKeyboardConfig& config) const {
+    using namespace Frontend;
+    KeyboardConfig frontend_config;
+    frontend_config.button_config = static_cast<ButtonConfig>(config.num_buttons_m1);
+    frontend_config.accept_mode = static_cast<AcceptedInput>(config.valid_input);
+    frontend_config.multiline_mode = config.multiline;
+    frontend_config.max_text_length = config.max_text_length;
+    frontend_config.max_digits = config.max_digits;
+    std::u16string buffer(config.hint_text.size(), 0);
+    std::memcpy(buffer.data(), config.hint_text.data(), config.hint_text.size() * sizeof(u16));
+    frontend_config.hint_text = Common::UTF16ToUTF8(buffer);
+    frontend_config.has_custom_button_text =
+        !std::all_of(config.button_text.begin(), config.button_text.end(),
+                     [](std::array<u16, HLE::Applets::MAX_BUTTON_TEXT_LEN + 1> x) {
+                         return std::all_of(x.begin(), x.end(), [](u16 x) { return x == 0; });
+                     });
+    if (frontend_config.has_custom_button_text) {
+        for (const auto& text : config.button_text) {
+            buffer.resize(text.size());
+            std::memcpy(buffer.data(), text.data(), text.size() * sizeof(u16));
+            frontend_config.button_text.push_back(Common::UTF16ToUTF8(buffer));
+        }
+    }
+    frontend_config.filters.prevent_digit =
+        static_cast<bool>(config.filter_flags & SoftwareKeyboardFilter::Digits);
+    frontend_config.filters.prevent_at =
+        static_cast<bool>(config.filter_flags & SoftwareKeyboardFilter::At);
+    frontend_config.filters.prevent_percent =
+        static_cast<bool>(config.filter_flags & SoftwareKeyboardFilter::Percent);
+    frontend_config.filters.prevent_backslash =
+        static_cast<bool>(config.filter_flags & SoftwareKeyboardFilter::Backslash);
+    frontend_config.filters.prevent_profanity =
+        static_cast<bool>(config.filter_flags & SoftwareKeyboardFilter::Profanity);
+    frontend_config.filters.enable_callback =
+        static_cast<bool>(config.filter_flags & SoftwareKeyboardFilter::Callback);
+    return frontend_config;
+}
 } // namespace Applets
 } // namespace HLE
diff --git a/src/core/hle/applets/swkbd.h b/src/core/hle/applets/swkbd.h
index f0cf320a33..d75d696ec4 100644
--- a/src/core/hle/applets/swkbd.h
+++ b/src/core/hle/applets/swkbd.h
@@ -6,6 +6,7 @@
 
 #include "common/common_funcs.h"
 #include "common/common_types.h"
+#include "core/frontend/applets/swkbd.h"
 #include "core/hle/applets/applet.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/shared_memory.h"
@@ -15,33 +16,156 @@
 namespace HLE {
 namespace Applets {
 
+/// Maximum number of buttons that can be in the keyboard.
+constexpr int MAX_BUTTON = 3;
+/// Maximum button text length, in UTF-16 code units.
+constexpr int MAX_BUTTON_TEXT_LEN = 16;
+/// Maximum hint text length, in UTF-16 code units.
+constexpr int MAX_HINT_TEXT_LEN = 64;
+/// Maximum filter callback error message length, in UTF-16 code units.
+constexpr int MAX_CALLBACK_MSG_LEN = 256;
+
+/// Keyboard types
+enum class SoftwareKeyboardType : u32 {
+    Normal,  ///< Normal keyboard with several pages (QWERTY/accents/symbol/mobile)
+    QWERTY,  ///< QWERTY keyboard only.
+    NumPad,  ///< Number pad.
+    Western, ///< On JPN systems, a text keyboard without Japanese input capabilities,
+             /// otherwise same as SWKBD_TYPE_NORMAL.
+};
+
+/// Keyboard dialog buttons.
+enum class SoftwareKeyboardButtonConfig : u32 {
+    SingleButton, ///< Ok button
+    DualButton,   ///< Cancel | Ok buttons
+    TripleButton, ///< Cancel | I Forgot | Ok buttons
+    NoButton,     ///< No button (returned by swkbdInputText in special cases)
+};
+
+/// Accepted input types.
+enum class SoftwareKeyboardValidInput : u32 {
+    Anything,         ///< All inputs are accepted.
+    NotEmpty,         ///< Empty inputs are not accepted.
+    NotEmptyNotBlank, ///< Empty or blank inputs (consisting solely of whitespace) are not
+                      /// accepted.
+    NotBlank, ///< Blank inputs (consisting solely of whitespace) are not accepted, but empty
+              /// inputs are.
+    FixedLen, ///< The input must have a fixed length (specified by maxTextLength in
+              /// swkbdInit).
+};
+
+/// Keyboard password modes.
+enum class SoftwareKeyboardPasswordMode : u32 {
+    None,      ///< Characters are not concealed.
+    Hide,      ///< Characters are concealed immediately.
+    HideDelay, ///< Characters are concealed a second after they've been typed.
+};
+
+/// Keyboard input filtering flags. Allows the caller to specify what input is explicitly not
+/// allowed
+namespace SoftwareKeyboardFilter {
+enum Filter {
+    Digits = 1,         ///< Disallow the use of more than a certain number of digits (0 or more)
+    At = 1 << 1,        ///< Disallow the use of the @ sign.
+    Percent = 1 << 2,   ///< Disallow the use of the % sign.
+    Backslash = 1 << 3, ///< Disallow the use of the \ sign.
+    Profanity = 1 << 4, ///< Disallow profanity using Nintendo's profanity filter.
+    Callback = 1 << 5,  ///< Use a callback in order to check the input.
+};
+} // namespace SoftwareKeyboardFilter
+
+/// Keyboard features.
+namespace SoftwareKeyboardFeature {
+enum Feature {
+    Parental = 1,             ///< Parental PIN mode.
+    DarkenTopScreen = 1 << 1, ///< Darken the top screen when the keyboard is shown.
+    PredictiveInput =
+        1 << 2,             ///< Enable predictive input (necessary for Kanji input in JPN systems).
+    Multiline = 1 << 3,     ///< Enable multiline input.
+    FixedWidth = 1 << 4,    ///< Enable fixed-width mode.
+    AllowHome = 1 << 5,     ///< Allow the usage of the HOME button.
+    AllowReset = 1 << 6,    ///< Allow the usage of a software-reset combination.
+    AllowPower = 1 << 7,    ///< Allow the usage of the POWER button.
+    DefaultQWERTY = 1 << 9, ///< Default to the QWERTY page when the keyboard is shown.
+};
+} // namespace SoftwareKeyboardFeature
+
+/// Keyboard filter callback return values.
+enum class SoftwareKeyboardCallbackResult : u32 {
+    OK,       ///< Specifies that the input is valid.
+    Close,    ///< Displays an error message, then closes the keyboard.
+    Continue, ///< Displays an error message and continues displaying the keyboard.
+};
+
+/// Keyboard return values.
+enum class SoftwareKeyboardResult : s32 {
+    None = -1,         ///< Dummy/unused.
+    InvalidInput = -2, ///< Invalid parameters to swkbd.
+    OutOfMem = -3,     ///< Out of memory.
+
+    D0Click = 0, ///< The button was clicked in 1-button dialogs.
+    D1Click0,    ///< The left button was clicked in 2-button dialogs.
+    D1Click1,    ///< The right button was clicked in 2-button dialogs.
+    D2Click0,    ///< The left button was clicked in 3-button dialogs.
+    D2Click1,    ///< The middle button was clicked in 3-button dialogs.
+    D2Click2,    ///< The right button was clicked in 3-button dialogs.
+
+    HomePressed = 10, ///< The HOME button was pressed.
+    ResetPressed,     ///< The soft-reset key combination was pressed.
+    PowerPressed,     ///< The POWER button was pressed.
+
+    ParentalOK = 20, ///< The parental PIN was verified successfully.
+    ParentalFail,    ///< The parental PIN was incorrect.
+
+    BannedInput = 30, ///< The filter callback returned SoftwareKeyboardCallback::CLOSE.
+};
+
 struct SoftwareKeyboardConfig {
-    INSERT_PADDING_WORDS(0x8);
-
-    u16 max_text_length; ///< Maximum length of the input text
-
-    INSERT_PADDING_BYTES(0x6E);
-
-    char16_t display_text[65]; ///< Text to display when asking the user for input
-
-    INSERT_PADDING_BYTES(0xE);
-
-    u32 default_text_offset; ///< Offset of the default text in the output SharedMemory
-
-    INSERT_PADDING_WORDS(0x3);
+    SoftwareKeyboardType type;
+    SoftwareKeyboardButtonConfig num_buttons_m1;
+    SoftwareKeyboardValidInput valid_input;
+    SoftwareKeyboardPasswordMode password_mode;
+    s32 is_parental_screen;
+    s32 darken_top_screen;
+    u32 filter_flags;
+    u32 save_state_flags;
+    u16 max_text_length;
+    u16 dict_word_count;
+    u16 max_digits;
+    std::array<std::array<u16, MAX_BUTTON_TEXT_LEN + 1>, MAX_BUTTON> button_text;
+    std::array<u16, 2> numpad_keys;
+    std::array<u16, MAX_HINT_TEXT_LEN + 1>
+        hint_text; ///< Text to display when asking the user for input
+    bool predictive_input;
+    bool multiline;
+    bool fixed_width;
+    bool allow_home;
+    bool allow_reset;
+    bool allow_power;
+    bool unknown;
+    bool default_qwerty;
+    std::array<bool, 4> button_submits_text;
+    u16 language;
 
+    u32 initial_text_offset; ///< Offset of the default text in the output SharedMemory
+    u32 dict_offset;
+    u32 initial_status_offset;
+    u32 initial_learning_offset;
     u32 shared_memory_size; ///< Size of the SharedMemory
+    u32 version;
 
-    INSERT_PADDING_WORDS(0x1);
+    SoftwareKeyboardResult return_code;
 
-    u32 return_code; ///< Return code of the SoftwareKeyboard, usually 2, other values are unknown
-
-    INSERT_PADDING_WORDS(0x2);
+    u32 status_offset;
+    u32 learning_offset;
 
     u32 text_offset; ///< Offset in the SharedMemory where the output text starts
     u16 text_length; ///< Length in characters of the output text
 
-    INSERT_PADDING_BYTES(0x2B6);
+    int callback_result;
+    std::array<u16, MAX_CALLBACK_MSG_LEN + 1> callback_msg;
+    bool skip_at_check;
+    INSERT_PADDING_BYTES(0xAB);
 };
 
 /**
@@ -71,6 +195,8 @@ public:
     void Finalize();
 
 private:
+    Frontend::KeyboardConfig ToFrontendConfig(const SoftwareKeyboardConfig& config) const;
+
     /// This SharedMemory will be created when we receive the LibAppJustStarted message.
     /// It holds the framebuffer info retrieved by the application with
     /// GSPGPU::ImportDisplayCaptureInfo
@@ -81,6 +207,8 @@ private:
 
     /// Configuration of this instance of the SoftwareKeyboard, as received from the application
     SoftwareKeyboardConfig config;
+
+    std::shared_ptr<Frontend::SoftwareKeyboard> frontend_applet;
 };
 } // namespace Applets
 } // namespace HLE