diff --git a/mo/web/jinja.py b/mo/web/jinja.py
index 893ac8ca01a617687a00fc49f20498b8843d7c73..f16c2e5c4efec31f9bfc7c637e1655456936775e 100644
--- a/mo/web/jinja.py
+++ b/mo/web/jinja.py
@@ -2,8 +2,10 @@
 
 from flask import url_for
 import json
+from html import escape
 from markupsafe import Markup
 from typing import Any
+import urllib.parse
 
 import mo.config as config
 import mo.db as db
@@ -67,6 +69,12 @@ def pion_link(u: db.User, contest_id: int) -> Markup:
     return Markup('<a href="{url}">{name}{test}</a>').format(url=url, name=u.full_name(), test=" (test)" if u.is_test else "")
 
 
+@app.template_filter()
+def mailto(email: str) -> Markup:
+    safe_email = urllib.parse.quote(email, safe='@')
+    return Markup(f'<a href="mailto:{escape(safe_email)}">{escape(email)}</a>')
+
+
 @app.template_filter()
 def or_dash(s: Any) -> str:
     return str(s) if s else '–'
diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py
index d788d9a0cc9a192325b18362f7a6c87ff769ddce..4e9ecf45452087c64547ca84faed46d62c4ebf61 100644
--- a/mo/web/org_contest.py
+++ b/mo/web/org_contest.py
@@ -30,7 +30,7 @@ from wtforms.widgets.html5 import NumberInput
 
 
 class ImportForm(FlaskForm):
-    file = flask_wtf.file.FileField("Soubor")
+    file = flask_wtf.file.FileField("Soubor", render_kw={'autofocus': True})
     typ = wtforms.SelectField(
         "Typ dat",
         choices=[(x.name, x.friendly_name()) for x in (ImportType.participants, ImportType.proctors, ImportType.judges)],
@@ -48,8 +48,8 @@ class ImportForm(FlaskForm):
 
 class ParticipantsFilterForm(PagerForm):
     school = wtforms.StringField("Škola")
-    participation_place = wtforms.StringField("Soutěžní místo")
-    contest_place = wtforms.StringField("Soutěžní oblast")
+    participation_place = wtforms.StringField("Soutěžní místo", render_kw={'autofocus': True})
+    contest_place = wtforms.StringField("Soutěžní oblast", render_kw={'autofocus': True})
     participation_state = wtforms.SelectField('Stav účasti', choices=[('*', '*')] + list(db.PartState.choices()), default='*')
 
     # format = wtforms.RadioField(choices=[('', 'Zobrazit'), ('csv', 'Stáhnout vše v CSV'), ('tsv', 'Stáhnout vše v TSV')])
@@ -656,7 +656,7 @@ def get_solution_context(contest_id: int, user_id: Optional[int], task_id: Optio
 
 
 class SubmitForm(FlaskForm):
-    note = wtforms.TextAreaField("Poznámka pro účastníka", description="Viditelná účastníkovi po uzavření kola")
+    note = wtforms.TextAreaField("Poznámka pro účastníka", description="Viditelná účastníkovi po uzavření kola", render_kw={'autofocus': True})
     org_note = wtforms.TextAreaField("Interní poznámka", description="Viditelná jen organizátorům")
     # Validátory k points budou přidány podle počtu maximálních bodů úlohy v org_submit_list
     points = IntegerField('Body', description="Účastník po uzavření kola uvidí jen naposledy zadané body")
@@ -887,7 +887,7 @@ def org_submit_list(contest_id: int, user_id: int, task_id: int, site_id: Option
 
 
 class SubmitEditForm(FlaskForm):
-    note = wtforms.TextAreaField("Poznámka pro účastníka", description="Viditelná účastníkovi po uzavření kola", render_kw={"rows": 8})
+    note = wtforms.TextAreaField("Poznámka pro účastníka", description="Viditelná účastníkovi po uzavření kola", render_kw={"rows": 8, 'autofocus': True})
     org_note = wtforms.TextAreaField("Interní poznámka", description="Viditelná jen organizátorům", render_kw={"rows": 8})
     submit = wtforms.SubmitField("Uložit")
 
@@ -1150,7 +1150,11 @@ def org_contest_solutions(id: int, site_id: Optional[int] = None):
 
 
 class DownloadSubmitsForm(FlaskForm):
-    min_points = wtforms.IntegerField('Minimální počet bodů v kole', description='Je-li uveden, řešení účastníků, kteří ve výsledcích celého kola dostali méně bodů, se nestahují.', validators=[validators.Optional()])
+    min_points = wtforms.IntegerField(
+        'Minimální počet bodů v kole', render_kw={'autofocus': True},
+        description='Je-li uveden, řešení účastníků, kteří ve výsledcích celého kola dostali méně bodů, se nestahují.',
+        validators=[validators.Optional()]
+    )
     download_sol = wtforms.SubmitField('Stáhnout všechna účastnická řešení')
     download_fb = wtforms.SubmitField('Stáhnout všechna opravená řešení')
     download_sol_mix = wtforms.SubmitField('Stáhnout účastnická/opravená')
@@ -1249,7 +1253,7 @@ def generic_batch_download(round: db.Round, contest: Optional[db.Contest], site:
 
 
 class UploadSubmitsForm(FlaskForm):
-    file = flask_wtf.file.FileField("Soubor", validators=[flask_wtf.file.FileRequired()])
+    file = flask_wtf.file.FileField("Soubor", validators=[flask_wtf.file.FileRequired()], render_kw={'autofocus': True})
     submit = wtforms.SubmitField('Odeslat')
 
 
@@ -1298,7 +1302,7 @@ def org_contest_task_upload(contest_id: int, task_id: int, site_id: Optional[int
 
 
 class BatchPointsForm(FlaskForm):
-    file = flask_wtf.file.FileField("Soubor")
+    file = flask_wtf.file.FileField("Soubor", render_kw={'autofocus': True})
     fmt = wtforms.SelectField(
         "Formát souboru",
         choices=FileFormat.choices(), coerce=FileFormat.coerce,
@@ -1404,7 +1408,11 @@ def org_contest_user(contest_id: int, user_id: int):
 
 
 class AdvanceForm(FlaskForm):
-    boundary = IntegerField('Bodová hranice', description="Postoupí všichni účastníci, kteří v minulém kole získali aspoň tolik bodů.", validators=[validators.InputRequired()])
+    boundary = IntegerField(
+        'Bodová hranice', render_kw={'autofocus': True},
+        description="Postoupí všichni účastníci, kteří v minulém kole získali aspoň tolik bodů.",
+        validators=[validators.InputRequired()]
+    )
     status = wtforms.HiddenField()
     preview = wtforms.SubmitField('Zobrazit návrh')
     execute = wtforms.SubmitField('Provést')
diff --git a/mo/web/org_place.py b/mo/web/org_place.py
index 322706b6b01e70630e98175ed4d6125bbea38d20..34fb5895887672b79a7583f4b71d6084103fa333 100644
--- a/mo/web/org_place.py
+++ b/mo/web/org_place.py
@@ -42,7 +42,7 @@ def org_place(id: int):
 
 class PlaceEditForm(FlaskForm):
     name = wtforms.StringField(
-        'Název',
+        'Název', render_kw={'autofocus': True},
         validators=[validators.DataRequired()]
     )
     code = wtforms.StringField(
@@ -169,7 +169,7 @@ def org_place_edit(id: int):
 
 
 class PlaceMoveForm(FlaskForm):
-    code = wtforms.StringField(validators=[validators.DataRequired()])
+    code = wtforms.StringField(validators=[validators.DataRequired()], render_kw={'autofocus': True})
     submit = wtforms.SubmitField('Najít místo')
     reset = wtforms.HiddenField()
     move = wtforms.HiddenField()
diff --git a/mo/web/org_round.py b/mo/web/org_round.py
index f14705d3b4aa9d138d7079279e0592147457743a..7dbc1c1abc04504d513cdc58ac3457f8660c0f31 100644
--- a/mo/web/org_round.py
+++ b/mo/web/org_round.py
@@ -225,9 +225,8 @@ def org_round(id: int):
 class TaskEditForm(FlaskForm):
     code = wtforms.StringField('Kód úlohy', validators=[
         validators.Required(),
-        # trik: nelze použít \w protože obsahuje _, \W je negace \w, takže [^\W-] je \w bez _
-        validators.Regexp(r'^([^\W_]|-)+$', message="Kód úlohy smí obsahovat jen písmena, čísla a znak -"),
-    ])
+        validators.Regexp(r'^[A-Za-z0-9-]+$', message="Kód úlohy smí obsahovat jen nediakritická písmena, čísla a znak -"),
+    ], render_kw={'autofocus': True})
     name = wtforms.StringField('Název úlohy')
     max_points = IntegerField(
         'Maximum bodů', validators=[validators.Optional()],
@@ -390,7 +389,7 @@ class MODateTimeField(wtforms.DateTimeField):
 
 
 class RoundEditForm(FlaskForm):
-    name = wtforms.StringField("Název")
+    name = wtforms.StringField("Název", render_kw={'autofocus': True})
     state = wtforms.SelectField("Stav kola", choices=db.RoundState.choices(), coerce=db.RoundState.coerce)
     # Only the desktop Firefox does not support datetime-local field nowadays,
     # other browsers does provide date and time picker UI :(
diff --git a/mo/web/org_users.py b/mo/web/org_users.py
index 5937abc2cd8c1c17e5b9e2ca9d89d8e582db3642..88354ecde8af6b7aa76613696cdf13fd052ad264 100644
--- a/mo/web/org_users.py
+++ b/mo/web/org_users.py
@@ -21,7 +21,7 @@ from mo.web.util import PagerForm
 
 class UsersFilterForm(PagerForm):
     # user
-    search_name = wtforms.TextField("Jméno/příjmení")
+    search_name = wtforms.TextField("Jméno/příjmení", render_kw={'autofocus': True})
     search_email = wtforms.TextField("E-mail")
 
     # participants
@@ -149,7 +149,7 @@ def org_users():
 
 class OrgsFilterForm(PagerForm):
     # user
-    search_name = wtforms.TextField("Jméno/příjmení")
+    search_name = wtforms.TextField("Jméno/příjmení", render_kw={'autofocus': True})
     search_email = wtforms.TextField("E-mail")
 
     # TODO: filtering by roles?
@@ -196,7 +196,7 @@ def org_orgs():
 
 
 class FormAddRole(FlaskForm):
-    role = wtforms.SelectField('Role',  choices=db.RoleType.choices(), coerce=db.RoleType.coerce)
+    role = wtforms.SelectField('Role',  choices=db.RoleType.choices(), coerce=db.RoleType.coerce, render_kw={'autofocus': True})
     place_code = wtforms.StringField('Oblast')
     year = wtforms.IntegerField('Ročník', validators=[validators.Optional()])
     category = wtforms.StringField("Kategorie", validators=[validators.Length(max=2)], filters=[lambda x: x or None])
@@ -320,7 +320,7 @@ def org_user(id: int):
 
 
 class UserEditForm(FlaskForm):
-    first_name = wtforms.StringField("Jméno", validators=[Required()])
+    first_name = wtforms.StringField("Jméno", validators=[Required()], render_kw={'autofocus': True})
     last_name = wtforms.StringField("Příjmení", validators=[Required()])
     note = wtforms.TextAreaField("Poznámka")
     submit = wtforms.SubmitField("Uložit")
diff --git a/mo/web/templates/base.html b/mo/web/templates/base.html
index 361c62f7f1b11baa946e9fbc51d6261b18418a15..a493fa9b5fd6fe5b1d02e62963f2a46e16043211 100644
--- a/mo/web/templates/base.html
+++ b/mo/web/templates/base.html
@@ -30,6 +30,7 @@
 </div>
 <main>
 {% if self.breadcrumbs() %}<ol class="breadcrumb">{% block breadcrumbs %}{% endblock %}</ol>{% endif %}
+{% block pretitle %}{% endblock %}
 <h2>{{ self.title() }}</h2>
 
 {% with messages = get_flashed_messages(with_categories=true) %}
@@ -50,7 +51,7 @@
 <footer>
 	<hr>
 	<div class="content">
-		<p>V případě problémů napište správci na adresu <a href='mailto:osmo@mo.mff.cuni.cz'>osmo@mo.mff.cuni.cz</a>.
+		<p>V případě problémů napište správci na adresu {{ 'osmo@mo.mff.cuni.cz'|mailto }}.
 		Více viz <a href='{{ url_for('doc_about') }}'>o aplikaci</a> a
 		<a href='{{ url_for('doc_gdpr') }}'>zpracování osobních údajů</a>.
 	</div>
diff --git a/mo/web/templates/doc_about.html b/mo/web/templates/doc_about.html
index 225a8edbaa920b0230ce03126b6f7fdf217e7a3a..f737e46822f32c67a37ca1372c07ad17159dbf6b 100644
--- a/mo/web/templates/doc_about.html
+++ b/mo/web/templates/doc_about.html
@@ -13,6 +13,6 @@
 MFF UK také děkujeme za poskytnutí serveru, kde systém běží.
 
 <p>Veškeré připomínky k chodu systému a nápady na další rozvoj
-prosím posílejte e-mailem na <a href='mailto:osmo@mo.mff.cuni.cz'>osmo@mo.mff.cuni.cz</a>.
+prosím posílejte e-mailem na {{ 'osmo@mo.mff.cuni.cz'|mailto }}.
 
 {% endblock %}
diff --git a/mo/web/templates/doc_gdpr.html b/mo/web/templates/doc_gdpr.html
index f4e0ae1f171cbaf57d452ab42ee8d2500201b877..f305feb49dc89b85286ea06cabea7ff19fd5cf09 100644
--- a/mo/web/templates/doc_gdpr.html
+++ b/mo/web/templates/doc_gdpr.html
@@ -19,7 +19,7 @@ Data nebudou předávána třetím stranám a budou uchovávána maximálně 2 r
 Tento odstavec se netýká zveřejněných výsledkových listin.
 
 <p>Souhlas se zpracováním osobních údajů můžete kdykoliv odvolat zasláním e-mailu na adresu
-<a href='mailto:osmo@mo.mff.cuni.cz'>osmo@mo.mff.cuni.cz</a>. Údaje z&nbsp;již zveřejněných výsledkových
+{{ 'osmo@mo.mff.cuni.cz'|mailto }}. Údaje z&nbsp;již zveřejněných výsledkových
 listin nicméně není možné zpětně odstranit.
 
 <p>Dále máte právo:
@@ -33,6 +33,6 @@ listin nicméně není možné zpětně odstranit.
 </ul>
 
 <p>V&nbsp;případě jakéhokoliv dotazu nebo uplatnění svých práv můžete kontaktovat univerzitního
-pověřence pro ochranu osobních údajů na e-mailové adrese <a href='mailto:gdpr@cuni.cz'>gdpr@cuni.cz</a>.
+pověřence pro ochranu osobních údajů na e-mailové adrese {{ 'gdpr@cuni.cz'|mailto }}.
 
 {% endblock %}
diff --git a/mo/web/templates/org_contest_solutions.html b/mo/web/templates/org_contest_solutions.html
index 623a82bc69c9b95e6d4ea27a5996c4bed16cf1da..0d4fe73a4a1eb1df77b75ffd36a6f9e063144b1b 100644
--- a/mo/web/templates/org_contest_solutions.html
+++ b/mo/web/templates/org_contest_solutions.html
@@ -10,14 +10,15 @@
 {{ contest_breadcrumbs(round=round, contest=contest, site=site, action="Založení řešení" if edit_form else "Tabulka řešení") }}
 {% endblock %}
 
-{% block body %}
+{% block pretitle %}
 {% if round.state in [RoundState.grading, RoundState.closed] %}
 <div class="btn-group pull-right">
 	<a class="btn btn-default" href="{{ url_for('org_score', contest_id=contest.contest_id) }}">Výsledky oblasti</a>
 	<a class="btn btn-default" href="{{ url_for('org_score', round_id=round.round_id) }}">Výsledky kola</a>
 </div>
 {% endif %}
-
+{% endblock %}
+{% block body %}
 {% include "parts/org_submit_warning.html" %}
 
 <p><i>
diff --git a/mo/web/templates/org_contest_task.html b/mo/web/templates/org_contest_task.html
index 0e4719576a7c2b0a3c455ed23543592576a42099..d327cf1275f8e35df504e021fea770f229d2c7ce 100644
--- a/mo/web/templates/org_contest_task.html
+++ b/mo/web/templates/org_contest_task.html
@@ -12,7 +12,7 @@
 {{ contest_breadcrumbs(round=round, contest=contest, site=site, task=task, action="Zadávání bodů" if points_form else "Založení řešení" if create_form else None) }}
 {% endblock %}
 
-{% block body %}
+{% block pretitle %}
 <div class="btn-group pull-right">
 	<a class="btn btn-default" href="{{ url_for('org_contest_solutions', id=ct_id, site_id=site_id) }}">Všechny úlohy</a>
 	{% if round.state in [RoundState.grading, RoundState.closed] %}
@@ -20,7 +20,8 @@
 	<a class="btn btn-default" href="{{ url_for('org_score', round_id=round.round_id) }}">Výsledky kola</a>
 	{% endif %}
 </div>
-
+{% endblock %}
+{% block body %}
 {% include "parts/org_submit_warning.html" %}
 
 {% set form = points_form or create_form %}
diff --git a/mo/web/templates/org_contest_user.html b/mo/web/templates/org_contest_user.html
index 89da5f3134a9c4cba7c6f5f4ad89f9d7c13248c6..f4a4944e46447e869d5fd3357d29cea9fa167447 100644
--- a/mo/web/templates/org_contest_user.html
+++ b/mo/web/templates/org_contest_user.html
@@ -32,7 +32,7 @@
 	</thead>
 	<tr><td>Jméno:<td>{{ user.first_name }}
 	<tr><td>Příjmení:<td>{{ user.last_name }}
-	<tr><td>E-mail:<td><a href="mailto:{{ user.email }}">{{ user.email }}</a>
+	<tr><td>E-mail:<td>{{ user.email|mailto }}
 	<tr><td>Škola:<td><a href='{{ url_for('org_place', id=pant.school) }}'>{{ pant.school_place.name }}</a>
 	<tr><td>Třída:<td>{{ pant.grade }}
 	<tr><td>Rok narození:<td>{{ pant.birth_year }}
diff --git a/mo/web/templates/org_org.html b/mo/web/templates/org_org.html
index beff61bc87e3c9475036c304d5718fedee8d4f07..3385df81f546e2978b649ac66d22f1b14563f241 100644
--- a/mo/web/templates/org_org.html
+++ b/mo/web/templates/org_org.html
@@ -6,7 +6,7 @@
 <table class=data>
 <tr><td>Jméno:<td>{{ user.first_name }}
 <tr><td>Příjmení:<td>{{ user.last_name }}
-<tr><td>E-mail:<td><a href="mailto:{{ user.email }}">{{ user.email }}</a>
+<tr><td>E-mail:<td>{{ user.email|mailto }}
 {% if user.is_admin %}<tr><td>Správce:<td>ano{% endif %}
 {% if user.is_org %}<tr><td>Organizátor:<td>ano{% endif %}
 <tr><td>Účet založen:<td>{{ user.created_at|timeformat }}
diff --git a/mo/web/templates/org_orgs.html b/mo/web/templates/org_orgs.html
index 25366f76117f8b47ea837cf3265703cfad48cb16..cfff4e6e3473c3c565ed4b60f22fc2ffcd377b6b 100644
--- a/mo/web/templates/org_orgs.html
+++ b/mo/web/templates/org_orgs.html
@@ -47,7 +47,7 @@
 {% for user in users %}
 	<tr>
 		<td>{{ user.first_name }}</td><td>{{ user.last_name }}</td>
-		<td><a href="mailto:{{ user.email }}">{{ user.email }}</a>{% if user.password_hash == None %}<span class="user-inactive" title='Účet dosud nebyl aktivován'> *</span>{% endif %}</td>
+		<td>{{ user.email|mailto }}{% if user.password_hash == None %}<span class="user-inactive" title='Účet dosud nebyl aktivován'> *</span>{% endif %}</td>
 		<td>{% if user.is_admin %}správce{% elif user.roles|count == 0 %}<i>žádná role</i>{% else %}
 			<ul>
 			{% for role in user.roles %}
diff --git a/mo/web/templates/org_score.html b/mo/web/templates/org_score.html
index 8c1872548411d8d3387e6c4f88546f91da0a4372..6cf67c92b7e0d605b74b02d3f7b9816d232aa9f8 100644
--- a/mo/web/templates/org_score.html
+++ b/mo/web/templates/org_score.html
@@ -7,13 +7,15 @@
 {{ contest_breadcrumbs(round=round, contest=contest, action="Výsledky oblasti" if contest else "Výsledky kola") }}
 {% endblock %}
 
-{% block body %}
+{% block pretitle %}
 <div class="btn-group pull-right">
 	{% if contest %}
 	<a class="btn btn-default" href="{{ url_for('org_contest_solutions', id=contest.contest_id) }}">Odevzdaná řešení</a>
 	<a class="btn btn-default" href="{{ url_for('org_score', round_id=round.round_id) }}">Výsledky kola</a>
 	{% endif %}
 </div>
+{% endblock %}
+{% block body %}
 
 {% if messages %}
 <div class="collapsible">
diff --git a/mo/web/templates/org_user.html b/mo/web/templates/org_user.html
index 83423526c2ea907f22466e2945117910aabb51e6..c068b09e789706551722ab0bbb42074c24621639 100644
--- a/mo/web/templates/org_user.html
+++ b/mo/web/templates/org_user.html
@@ -6,7 +6,7 @@
 <table class=data>
 <tr><td>Jméno:<td>{{ user.first_name }}
 <tr><td>Příjmení:<td>{{ user.last_name }}
-<tr><td>E-mail:<td><a href="mailto:{{ user.email }}">{{ user.email }}</a>
+<tr><td>E-mail:<td>{{ user.email|mailto }}
 {% if user.is_admin %}<tr><td>Správce:<td>ano{% endif %}
 {% if user.is_org %}<tr><td>Organizátor:<td>ano{% endif %}
 <tr><td>Účet založen:<td>{{ user.created_at|timeformat }}
diff --git a/mo/web/templates/org_user_edit.html b/mo/web/templates/org_user_edit.html
index e4841fdb023f5d36450d594b5a203e3ce30ef430..db299584495544a3fbcf001bf98841ca5b60b54a 100644
--- a/mo/web/templates/org_user_edit.html
+++ b/mo/web/templates/org_user_edit.html
@@ -6,7 +6,7 @@
 <table class=data>
 <tr><td>Jméno:</td><td>{{ user.first_name }}</td></tr>
 <tr><td>Příjmení:</td><td>{{ user.last_name }}</td></tr>
-<tr><td>E-mail:</td><td><a href="mailto:{{ user.email }}">{{ user.email }}</a></td></tr>
+<tr><td>E-mail:</td><td>{{ user.email|mailto }}</td></tr>
 {% if user.is_admin %}<tr><td>Správce:</td><td>ano</td></tr>{% endif %}
 {% if user.is_org %}<tr><td>Organizátor:</td><td>ano</td></tr>{% endif %}
 <tr><td>Poznámka:</td><td style="white-space: pre;">{{ user.note }}</td></tr>
diff --git a/mo/web/templates/org_users.html b/mo/web/templates/org_users.html
index 776293f556d6bbb3f026e208111c642a46e966bc..ad5100ef49d4de39a604787a04e2538d32b58603 100644
--- a/mo/web/templates/org_users.html
+++ b/mo/web/templates/org_users.html
@@ -71,7 +71,7 @@
 {% for user in users %}
 	<tr{% if user.is_test %} class="testuser" title="Testovací uživatel"{% endif %}>
 		<td>{{ user.first_name }}</td><td>{{ user.last_name }}</td>
-		<td><a href="mailto:{{ user.email }}">{{ user.email }}</a>{% if user.password_hash == None %}<span class="user-inactive" title='Účet dosud nebyl aktivován'> *</span>{% endif %}</td>
+		<td>{{ user.email|mailto }}{% if user.password_hash == None %}<span class="user-inactive" title='Účet dosud nebyl aktivován'> *</span>{% endif %}</td>
 		<td>{% if user.participants|count == 0 %}<i>v žádném ročníku</i>{% else %}
 			<ul>
 			{% for participant in user.participants %}
diff --git a/mo/web/templates/parts/org_solution_table.html b/mo/web/templates/parts/org_solution_table.html
index 3b9c625c98bce7448602b47de839301eebf8a19a..7e164d06200a77d582d1fe548df93bbadb5cd307 100644
--- a/mo/web/templates/parts/org_solution_table.html
+++ b/mo/web/templates/parts/org_solution_table.html
@@ -30,6 +30,7 @@ finální (ve výchozím stavu poslední nahrané).{% elif sc.allow_upload_solut
 			<th>Akce
 		</tr>
 	</thead>
+{% set tabindex = namespace(value=1) %}
 {% for obj, sol in rows %}
 	{% set u = for_user or obj.user %}
 	{% set task = for_task or obj %}
@@ -85,14 +86,16 @@ finální (ve výchozím stavu poslední nahrané).{% elif sc.allow_upload_solut
 			{% if sol.org_note %} <span class="icon" title="Interní poznámka: {{ sol.org_note }}">🗩</span>{% endif %}
 		<td>
 			{% if points_form %}
-				<input type="number" min=0 {% if task.max_points is not none %}max={{ task.max_points }}{% endif %} class="form-control" name="points_{{u.user_id}}" value="{{ request_form.get("points_{}".format(u.user_id)) or sol.points }}" size="4">
+				<input type="number" min=0 {% if task.max_points is not none %}max={{ task.max_points }}{% endif %} class="form-control" name="points_{{u.user_id}}" value="{{ request_form.get("points_{}".format(u.user_id)) or sol.points }}" size="4" tabindex={{ tabindex.value }} autofocus>
+				{% set tabindex.value = tabindex.value + 1%}
 			{% else %}
 				{% if sol.points is not none %}{{ sol.points}}{% else %}<span class="unknown">?</span>{% endif %}
 			{% endif %}
 		{% else %}
 		<td colspan="4" class="text-center">
 			{% if create_form %}
-			<input type="checkbox" name="create_sol_{{u.user_id}}" id="create_sol_{{u.user_id}}"{% if request_form.get("create_sol_{}".format(u.user_id)) %}checked{% endif %}>
+			<input type="checkbox" name="create_sol_{{u.user_id}}" id="create_sol_{{u.user_id}}"{% if request_form.get("create_sol_{}".format(u.user_id)) %}checked{% endif %} tabindex={{ tabindex.value }} autofocus>
+			{% set tabindex.value = tabindex.value + 1%}
 			<label for="create_sol_{{u.user_id}}">Založit řešení</label>
 			{% else %}–{% endif %}
 		{% endif %}
diff --git a/mo/web/user.py b/mo/web/user.py
index 5ebc54f2f02a4d563747a16c48f07014112da557..e0b56ac0c64df8eb140932bfaa8ccb6a3f7b94e4 100644
--- a/mo/web/user.py
+++ b/mo/web/user.py
@@ -97,7 +97,7 @@ def user_task_statement(id: int):
 
 
 class SubmitForm(FlaskForm):
-    file = flask_wtf.file.FileField("Soubor", validators=[flask_wtf.file.FileRequired()])
+    file = flask_wtf.file.FileField("Soubor", validators=[flask_wtf.file.FileRequired()], render_kw={'autofocus': True})
     note = wtforms.TextAreaField("Poznámka", description="Zde můžete něco vzkázat organizátorům soutěže.")
     submit = wtforms.SubmitField('Odevzdat')