diff --git a/mo/web/org.py b/mo/web/org.py
index 1158d6ddd6ebdaac438269cc48d080a88d6f9ffe..e27f3587ade8e6c2e6a70b9587400f91585095be 100644
--- a/mo/web/org.py
+++ b/mo/web/org.py
@@ -215,5 +215,10 @@ def org_export_schools():
                 'kraj_code': k.get_code(),
             })
 
-    table = Table(school_export_columns, gen_rows(), 'skoly')
+    table = Table(
+        id="skoly",
+        columns=school_export_columns,
+        rows=gen_rows(),
+        filename='skoly',
+    )
     return table.send_as(format, streaming=True)
diff --git a/mo/web/org_contest.py b/mo/web/org_contest.py
index 1bd3528163915fe5b5fa1100feeec9e827af48bd..fccf0413b99fd7aa864871a02549072b6452ec6a 100644
--- a/mo/web/org_contest.py
+++ b/mo/web/org_contest.py
@@ -551,13 +551,14 @@ def org_generic_list(round_id: Optional[int] = None, hier_id: Optional[int] = No
             filter=filter, count=count, action_form=action_form,
         )
     else:
-        table = make_contestant_table(query, round, is_export=True)
-        return table.send_as(format)
+        table = make_contestant_table(query, round)
+        return table.send_as(format, args=request.args)
 
 
 contest_list_columns = (
     Column(key='first_name',    name='krestni',     title='Křestní jméno'),
     Column(key='last_name',     name='prijmeni',    title='Příjmení'),
+    Column(key='user_id',       name='user_id',     title='ID uživatele', in_html=False),
     Column(key='email',         name='email',       title='E-mail'),
     Column(key='school',        name='skola',       title='Škola'),
     Column(key='school_code',   name='kod_skoly',   title='Kód školy'),
@@ -608,7 +609,7 @@ def get_contestants_query(
     return query
 
 
-def make_contestant_table(query: Query, round: db.Round, add_checkbox: bool = False, add_contest_column: bool = False, is_export: bool = False):
+def make_contestant_table(query: Query, round: db.Round, add_checkbox: bool = False, add_contest_column: bool = False):
     ctants = query.all()
 
     rows: List[Row] = []
@@ -640,13 +641,12 @@ def make_contestant_table(query: Query, round: db.Round, add_checkbox: bool = Fa
 
     cols: List[Column] = list(contest_list_columns)
     if add_checkbox:
-        cols = [Column(key='checkbox', name=' ', title=' ')] + cols
+        cols = [Column(key='checkbox', name=' ', title=' ', in_export=None)] + cols
     if add_contest_column:
         cols.append(Column(key='region_code', name='kod_oblasti', title=round.get_level().name.title()))
-    if is_export:
-        cols.append(Column(key='user_id', name='user_id'))
 
     return Table(
+        id="ucastnici",
         columns=cols,
         rows=rows,
         filename='ucastnici',
diff --git a/mo/web/org_score.py b/mo/web/org_score.py
index 44642e51651f760e7a9da48d605f0d42c53253b3..4f6eafef0d818941bf189bd51bd0d52fb5788d6b 100644
--- a/mo/web/org_score.py
+++ b/mo/web/org_score.py
@@ -141,22 +141,22 @@ def org_score(round_id: Optional[int] = None, hier_id: Optional[int] = None, ct_
         subcontest_id_map[(subcontest.round_id, subcontest.master_contest_id)] = subcontest.contest_id
 
     # Construct columns
-    is_export = (format != "")
-    columns = []
-    columns.append(Column(key='order', name='poradi', title='Pořadí'))
-    if is_export:
-        columns.append(Column(key='status', name='stav'))
-    columns.append(Column(key='participant', name='ucastnik', title='Účastník'))
-    if is_export:
-        columns.append(Column(key='email', name='email'))
+    columns = [
+        Column(key='order',         name='poradi',          title='Pořadí'),
+        Column(key='status',        name='stav',            title='Stav (vítěz, …)', in_html=False),
+        Column(key='participant',   name='ucastnik',        title='Účastník',       in_export=False),
+        Column(key='first_name',    name='krestni',         title='Křestní jméno',  in_html=False),
+        Column(key='last_name',     name='prijmeni',        title='Příjmení',       in_html=False),
+        Column(key='email',         name='email',           title='E-mail',         in_html=False),
+    ]
     if not ct_id:
-        columns.append(Column(key='contest', name='oblast', title=round.get_level().name.title()))
-    if is_export:
-        columns.append(Column(key='pion_place', name='soutezni_misto'))
-    columns.append(Column(key='school', name='skola', title='Škola'))
-    columns.append(Column(key='grade', name='rocnik', title='Ročník'))
-    if is_export:
-        columns.append(Column(key='birth_year', name='rok_narozeni'))
+        columns.append(Column(key='contest', name='oblast', title='Soutěžní ' + round.get_level().name))
+    columns.extend([
+        Column(key='pion_place',    name='soutezni_misto',  title='Soutěžní místo', in_html=False),
+        Column(key='school',        name='skola',           title='Škola'),
+        Column(key='grade',         name='rocnik',          title='Ročník'),
+        Column(key='birth_year',    name='rok_narozeni',    title='Rok narození',   in_html=False),
+    ])
     for task in tasks:
         title = task.code
         if contest:
@@ -173,7 +173,7 @@ def org_score(round_id: Optional[int] = None, hier_id: Optional[int] = None, ct_
     columns.append(Column(key='total_points', name='celkove_body', title='Celkové body'))
     if is_edit:
         columns.append(Column(key='suborder', name='zjednoznacneni_poradi', title='Zjednoznačnění'))
-    # columns.append(Column(key='order_key', name='order_key', title='Třídící klíč'))
+    columns.append(Column(key='order_key', name='order_key', title='Třídící klíč', in_html=False, in_export=False))
 
     # Construct rows
     table_rows = []
@@ -199,6 +199,8 @@ def org_score(round_id: Optional[int] = None, hier_id: Optional[int] = None, ct_
             'order':        order_cell,
             'status':       status,
             'user':         user,
+            'first_name':   user.first_name,
+            'last_name':    user.last_name,
             'email':        user.email,
             'participant':  cell_pion_link(user, local_pion_ct_id, user.full_name()),
             'contest':      CellLink(pion.contest.place.name or "?", url_for('org_contest', ct_id=pion.contest_id)),
@@ -228,6 +230,7 @@ def org_score(round_id: Optional[int] = None, hier_id: Optional[int] = None, ct_
     if contest:
         filename += f"_oblast_{contest.place.code or contest.place.place_id}"
     table = Table(
+        id="vysledky",
         table_class="data full center",
         columns=columns,
         rows=table_rows,
@@ -254,7 +257,7 @@ def org_score(round_id: Optional[int] = None, hier_id: Optional[int] = None, ct_
             edit_form=edit_form, snapshot_form=snapshot_form,
         )
     else:
-        return table.send_as(format)
+        return table.send_as(format, args=request.args)
 
 
 class SetFinalScoretableForm(FlaskForm):
@@ -401,11 +404,12 @@ def org_score_snapshot(ct_id: int, scoretable_id: int):
     if not scoretable or scoretable.contest_id != ct_id:
         raise werkzeug.exceptions.NotFound()
 
-    columns, table_rows = scoretable_construct(scoretable, format != "")
+    columns, table_rows = scoretable_construct(scoretable)
     # columns.append(Column(key='order_key', name='order_key', title='Třídící klíč'))
 
     filename = f"vysledky_{ctx.round.year}-{ctx.round.category}-{ctx.round.level}_oblast_{ctx.contest.place.code or ctx.contest.place.place_id}"
     table = Table(
+        id="vysledky",
         table_class="data full center",
         columns=columns,
         rows=table_rows,
diff --git a/mo/web/table.py b/mo/web/table.py
index f95571a34282f974d6f807535a59c97e55b69e9f..65fdde48fd4f6f65e15967102760dd351c0283ff 100644
--- a/mo/web/table.py
+++ b/mo/web/table.py
@@ -4,8 +4,9 @@ from flask import Response, url_for
 from html import escape
 import io
 from markupsafe import Markup
-from typing import Any, Dict, Sequence, Optional, Iterable, Union
+from typing import Any, Dict, List, Sequence, Optional, Iterable, Union
 import urllib.parse
+from werkzeug.datastructures import ImmutableMultiDict
 import werkzeug.exceptions
 
 from mo.csv import FileFormat
@@ -19,11 +20,15 @@ class Column:
     key: str                # Jméno klíče ve slovnících
     name: str               # Název v hlavičce CSV
     title: str              # Titulek v HTML tabulkách
+    in_html: bool           # Jestli sloupec zobrazit na webu
+    in_export: Optional[bool] # Jestli sloupec defaultně exportovat, None = ani nenabízet
 
-    def __init__(self, key: str, name: Optional[str] = None, title: Optional[str] = None):
+    def __init__(self, key: str, name: Optional[str] = None, title: Optional[str] = None, in_html: bool = True, in_export: Optional[bool] = True):
         self.key = key
         self.name = name or key
         self.title = title or self.name
+        self.in_html = in_html
+        self.in_export = in_export
 
 
 class Cell:
@@ -142,6 +147,7 @@ class OrderCell(Cell):
 
 
 class Table:
+    id: str  # validní jako HTML id a unikátní pro tabulky zobrazené na stejné stránce
     columns: Sequence[Column]
     rows: Iterable[Row]
     filename: str
@@ -149,10 +155,11 @@ class Table:
     table_class: str
 
     def __init__(
-        self, columns: Sequence[Column], rows: Iterable[Row],
+        self, id: str, columns: Sequence[Column], rows: Iterable[Row],
         filename: str, show_downlink: bool = True,
         table_class: str = "data"
     ):
+        self.id = id
         self.columns = columns
         self.rows = rows
         self.filename = filename
@@ -160,9 +167,11 @@ class Table:
         self.table_class = table_class
 
     def to_html(self) -> str:
-        tab = [f'<table class="{self.table_class}">', '<thead>', '<tr>']
+        tab = [f'<table class="{self.table_class}" id="{self.id}">', '<thead>', '<tr>']
 
         for c in self.columns:
+            if not c.in_html:
+                continue
             tab.append(f'\t<th>{c.title}')
 
         tab.append('<tbody>')
@@ -171,6 +180,8 @@ class Table:
             r_attr = [f' {key}="{val}"' for (key, val) in r.html_attr.items()]
             tab.append(f'<tr{"".join(r_attr)}>')
             for c in self.columns:
+                if not c.in_html:
+                    continue
                 val = r.get(c.key)
                 if isinstance(val, Cell):
                     tab.append(val.to_html())
@@ -179,32 +190,56 @@ class Table:
 
         tab.append('</table>')
         if self.show_downlink:
-            tab.append("<p>Stáhnout jako <a href='?format=en_csv'>CSV s čárkami</a>, <a href='?format=cs_csv'>CSV se středníky</a> nebo <a href='?format=tsv'>TSV</a>.")
+            id = 'column-selection-toggle-' + self.id
+            tab.extend([
+                '<form method="GET">',
+                '   <p>Stáhnout jako <button class="btn btn-default btn-xs" name="format" value="en_csv">CSV s čárkami</button>,',
+                '   <button class="btn btn-default btn-xs" name="format" value="cs_csv">CSV se středníky</button> nebo <button class="btn btn-default btn-xs" name="format" value="tsv">TSV</button>.',
+                f'   <div class="collapsible"><input type="checkbox" class="toggle" id="{id}">',
+                f'       <label for="{id}" class="toggle toggle-small">Vybrat sloupce pro stažení</label>',
+                '       <div class="collapsible-inner"><div class="form-frame">',
+                self.get_columns_checkboxes(line_prefix="            "),
+                '        </div></div>',
+                '   </div>',
+                '</form>',
+            ])
+
         return Markup("\n".join(tab))
 
-    def to_csv(self, fmt: FileFormat) -> bytes:
+    def get_columns_checkboxes(self, line_prefix: str = "", args: Optional[ImmutableMultiDict] = None) -> Markup:
+        out = [line_prefix + '<input type="hidden" name="do_column_selection" value="1">']
+        for c in self.columns:
+            if c.in_export is None:
+                continue
+            checked = c.in_export
+            if args is not None and args.get('do_column_selection') is not None:
+                checked = args.get('column_' + c.name) is not None
+            out.append(f'{line_prefix}<div class="checkbox show"><label><input name="column_{c.name}" type="checkbox"{" checked" * checked} value="1">{c.title} <small>(<code>{c.name}</code></small>)</label></div>')
+        return Markup("\n".join(out))
+
+    def to_csv(self, fmt: FileFormat, export_columns: List[Column]) -> bytes:
         out = io.StringIO()
         writer = csv.writer(out, dialect=fmt.get_dialect())
 
-        header = [c.name for c in self.columns]
+        header = [c.name for c in export_columns]
         writer.writerow(header)
 
         for row in self.rows:
-            r = [row.get(c.key) for c in self.columns]
+            r = [row.get(c.key) for c in export_columns]
             writer.writerow(r)
 
         return out.getvalue().encode(fmt.get_charset())
 
-    def to_csv_stream(self, fmt: FileFormat) -> Iterable[bytes]:
+    def to_csv_stream(self, fmt: FileFormat, export_columns: List[Column]) -> Iterable[bytes]:
         out = io.StringIO()
         writer = csv.writer(out, dialect=fmt.get_dialect())
 
-        header = [c.name for c in self.columns]
+        header = [c.name for c in export_columns]
         writer.writerow(header)
 
         nrows = 0
         for row in self.rows:
-            r = [row.get(c.key) for c in self.columns]
+            r = [row.get(c.key) for c in export_columns]
             writer.writerow(r)
 
             nrows += 1
@@ -216,16 +251,23 @@ class Table:
 
         yield out.getvalue().encode(fmt.get_charset())
 
-    def send_as(self, format: Union[FileFormat, str], streaming: bool = False) -> Response:
+    def send_as(self, format: Union[FileFormat, str], streaming: bool = False, args: Optional[ImmutableMultiDict] = None) -> Response:
         try:
             fmt = FileFormat.coerce(format)
         except ValueError:
             raise werkzeug.exceptions.NotFound()
 
+        export_columns = [c for c in self.columns if c.in_export]
+        if args is not None and args.get('do_column_selection') is not None:
+            export_columns = [c for c in self.columns if args.get('column_' + c.name) is not None]
+
+        if len(export_columns) == 0:
+            raise werkzeug.exceptions.BadRequest("Žádné sloupce v exportu, musíte zvolit alespoň jeden")
+
         if streaming:
-            resp = Response(self.to_csv_stream(fmt))
+            resp = Response(self.to_csv_stream(fmt, export_columns))
         else:
-            out = self.to_csv(fmt)
+            out = self.to_csv(fmt, export_columns)
             resp = app.make_response(out)
 
         resp.content_type = fmt.get_content_type()
diff --git a/mo/web/templates/org_generic_list.html b/mo/web/templates/org_generic_list.html
index 379c7a3ab1cafc5b8cb435f30ae37be0e4b20c17..40785f472d1df04143e32c575ef3b9830901b8af 100644
--- a/mo/web/templates/org_generic_list.html
+++ b/mo/web/templates/org_generic_list.html
@@ -63,6 +63,14 @@
 		Celkem <b>{{count|inflected('nalezený účastník', 'nalezení účastníci', 'nalezených účastníků')}}</b>.
 		{% endif %}
 	</div>
+	<div class="form-row">
+		<div class="collapsible"><input type="checkbox" class="toggle" id="column-selection-toggle">
+			<label for="column-selection-toggle" class="toggle toggle-small">Vybrat sloupce pro stažení</label>
+			<div class="collapsible-inner"><div class="form-frame">
+				{{ table.get_columns_checkboxes(line_prefix="\t\t\t\t", args=request.args) }}
+			</div></div>
+		</div>
+	</div>
 </form>
 </div>
 
diff --git a/mo/web/templates/org_score.html b/mo/web/templates/org_score.html
index a725ddb86d5913f35ae49f8be059e8ea7b66d0e6..177920c85e58ffb05d5144894847f811c9837e79 100644
--- a/mo/web/templates/org_score.html
+++ b/mo/web/templates/org_score.html
@@ -38,7 +38,7 @@
 				{% elif type == "warning" %}<li>Varování: {{ msg }}
 				{% else %}<li class="text-info">Info: {{ msg }}{% endif %}
 			{% endfor %}
-			</ul></div>
+			</ul>
 		</div>
 	</div>
 </div>
diff --git a/mo/web/user.py b/mo/web/user.py
index 93a21b40311aeee80e6907b26473885636835e00..fd56284a3f85f96a205bf1861a2a5d7ee25214fd 100644
--- a/mo/web/user.py
+++ b/mo/web/user.py
@@ -495,18 +495,17 @@ def user_paper(contest_id: int, paper_id: int):
     return mo.web.util.send_task_paper(paper)
 
 
-def scoretable_construct(scoretable: db.ScoreTable, is_export: bool = False) -> Tuple[List[Column], List[Row]]:
+def scoretable_construct(scoretable: db.ScoreTable) -> Tuple[List[Column], List[Row]]:
     """Pro konstrukci výsledkovky zobrazované soutěžícím. Využito i při zobrazení
     uložených snapshotů výsledkovky v org_score.py.
     """
     columns = [
-        Column(key='order', name='poradi', title='Pořadí'),
-        Column(key='name', name='ucastnik', title='Účastník'),
-        Column(key='school', name='skola', title='Škola'),
-        Column(key='grade', name='rocnik', title='Ročník')
+        Column(key='order',  name='poradi',   title='Pořadí'),
+        Column(key='status', name='stav',     title='Stav účasti', in_html=False),
+        Column(key='name',   name='ucastnik', title='Účastník'),
+        Column(key='school', name='skola',    title='Škola'),
+        Column(key='grade',  name='rocnik',   title='Ročník'),
     ]
-    if is_export:
-        columns.insert(1, Column(key='status', name='stav'))
 
     for (code, name) in scoretable.tasks:
         columns.append(Column(key=f'task_{code}', name=code, title=code))
@@ -544,11 +543,12 @@ def user_contest_score(id: int):
     if not contest.scoretable or state not in [db.RoundState.graded, db.RoundState.closed]:
         raise werkzeug.exceptions.NotFound()
 
-    columns, table_rows = scoretable_construct(contest.scoretable, format != "")
+    columns, table_rows = scoretable_construct(contest.scoretable)
     # columns.append(Column(key='order_key', name='order_key', title='Třídící klíč'))
 
     filename = f"vysledky_{round.year}-{round.category}-{round.level}_oblast_{contest.place.code or contest.place.place_id}"
     table = Table(
+        id="vysledky",
         table_class="data full center",
         columns=columns,
         rows=table_rows,
diff --git a/static/mo.css b/static/mo.css
index 7fb589d20d9b0db4a5d179538c5f94b98e08c51e..5f368cdca37000dc01c85f70f35f5350f2c540ab 100644
--- a/static/mo.css
+++ b/static/mo.css
@@ -388,6 +388,10 @@ div.alert + div.alert {
 	cursor: pointer;
 	margin-left: 15px;
 }
+.collapsible label.toggle-small {
+	font-weight: normal;
+	margin-bottom: 0px;
+}
 .collapsible label.toggle::before {
 	position: absolute;
 	content: "";