diff --git a/CHANGELOG.md b/CHANGELOG.md
index bface6c86d3f..ceff78b10730 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
 
 ## Unreleased
 
+* Expose object of which slots have received content in `$$slots` ([#2106](https://github.com/sveltejs/svelte/issues/2106))
 * Add types to `createEventDispatcher` ([#5211](https://github.com/sveltejs/svelte/issues/5211))
 * In SSR mode, do not automatically declare variables for reactive assignments to member expressions ([#5247](https://github.com/sveltejs/svelte/issues/5247))
 * Include selector in message of `unused-css-selector` warning ([#5252](https://github.com/sveltejs/svelte/issues/5252))
diff --git a/src/compiler/compile/render_dom/Renderer.ts b/src/compiler/compile/render_dom/Renderer.ts
index c02d646ebf74..5092626d25f5 100644
--- a/src/compiler/compile/render_dom/Renderer.ts
+++ b/src/compiler/compile/render_dom/Renderer.ts
@@ -60,7 +60,7 @@ export default class Renderer {
 
 		if (component.slots.size > 0) {
 			this.add_to_context('$$scope');
-			this.add_to_context('$$slots');
+			this.add_to_context('#slots');
 		}
 
 		if (this.binding_groups.size > 0) {
diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts
index 7d0dce831536..4ee9abae49b3 100644
--- a/src/compiler/compile/render_dom/index.ts
+++ b/src/compiler/compile/render_dom/index.ts
@@ -70,6 +70,15 @@ export default function dom(
 		);
 	}
 
+	const uses_slots = component.var_lookup.has('$$slots');
+	let compute_slots;
+	if (uses_slots) {
+		compute_slots = b`
+			const $$slots = @compute_slots(#slots);
+		`;
+	}
+
+
 	const uses_props = component.var_lookup.has('$$props');
 	const uses_rest = component.var_lookup.has('$$restProps');
 	const $$props = uses_props || uses_rest ? `$$new_props` : `$$props`;
@@ -409,13 +418,14 @@ export default function dom(
 
 				${resubscribable_reactive_store_unsubscribers}
 
+				${component.slots.size || component.compile_options.dev || uses_slots ? b`let { $$slots: #slots = {}, $$scope } = $$props;` : null}
+				${component.compile_options.dev && b`@validate_slots('${component.tag}', #slots, [${[...component.slots.keys()].map(key => `'${key}'`).join(',')}]);`}
+				${compute_slots}
+
 				${instance_javascript}
 
 				${unknown_props_check}
 
-				${component.slots.size || component.compile_options.dev ? b`let { $$slots = {}, $$scope } = $$props;` : null}
-				${component.compile_options.dev && b`@validate_slots('${component.tag}', $$slots, [${[...component.slots.keys()].map(key => `'${key}'`).join(',')}]);`}
-
 				${renderer.binding_groups.size > 0 && b`const $$binding_groups = [${[...renderer.binding_groups.keys()].map(_ => x`[]`)}];`}
 
 				${component.partly_hoisted}
diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts
index bf808196c0b1..5c02e78158c5 100644
--- a/src/compiler/compile/render_dom/wrappers/Slot.ts
+++ b/src/compiler/compile/render_dom/wrappers/Slot.ts
@@ -125,7 +125,7 @@ export default class SlotWrapper extends Wrapper {
 		const slot_or_fallback = has_fallback ? block.get_unique_name(`${sanitize(slot_name)}_slot_or_fallback`) : slot;
 
 		block.chunks.init.push(b`
-			const ${slot_definition} = ${renderer.reference('$$slots')}.${slot_name};
+			const ${slot_definition} = ${renderer.reference('#slots')}.${slot_name};
 			const ${slot} = @create_slot(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn});
 			${has_fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null}
 		`);
diff --git a/src/compiler/compile/render_ssr/handlers/Slot.ts b/src/compiler/compile/render_ssr/handlers/Slot.ts
index 3b1e199c75ed..32b6246e6445 100644
--- a/src/compiler/compile/render_ssr/handlers/Slot.ts
+++ b/src/compiler/compile/render_ssr/handlers/Slot.ts
@@ -11,8 +11,8 @@ export default function(node: Slot, renderer: Renderer, options: RenderOptions)
 	const result = renderer.pop();
 
 	renderer.add_expression(x`
-		$$slots.${node.slot_name}
-			? $$slots.${node.slot_name}(${slot_data})
+		#slots.${node.slot_name}
+			? #slots.${node.slot_name}(${slot_data})
 			: ${result}
 	`);
 }
diff --git a/src/compiler/compile/render_ssr/index.ts b/src/compiler/compile/render_ssr/index.ts
index a72b8b35aec1..ff45abd78b05 100644
--- a/src/compiler/compile/render_ssr/index.ts
+++ b/src/compiler/compile/render_ssr/index.ts
@@ -34,6 +34,9 @@ export default function ssr(
 	const props = component.vars.filter(variable => !variable.module && variable.export_name);
 	const rest = uses_rest ? b`let $$restProps = @compute_rest_props($$props, [${props.map(prop => `"${prop.export_name}"`).join(',')}]);` : null;
 
+	const uses_slots = component.var_lookup.has('$$slots');
+	const slots = uses_slots ? b`let $$slots = @compute_slots(#slots);` : null;
+
 	const reactive_stores = component.vars.filter(variable => variable.name[0] === '$' && variable.name[1] !== '$');
 	const reactive_store_values = reactive_stores
 		.map(({ name }) => {
@@ -118,6 +121,7 @@ export default function ssr(
 
 	const blocks = [
 		rest,
+		slots,
 		...reactive_stores.map(({ name }) => {
 			const store_name = name.slice(1);
 			const store = component.var_lookup.get(store_name);
@@ -144,7 +148,7 @@ export default function ssr(
 
 		${component.fully_hoisted}
 
-		const ${name} = @create_ssr_component(($$result, $$props, $$bindings, $$slots) => {
+		const ${name} = @create_ssr_component(($$result, $$props, $$bindings, #slots) => {
 			${blocks}
 		});
 	`;
diff --git a/src/compiler/compile/utils/reserved_keywords.ts b/src/compiler/compile/utils/reserved_keywords.ts
index 75825c17193b..d6eb8b96739d 100644
--- a/src/compiler/compile/utils/reserved_keywords.ts
+++ b/src/compiler/compile/utils/reserved_keywords.ts
@@ -1,4 +1,4 @@
-export const reserved_keywords = new Set(["$$props", "$$restProps"]);
+export const reserved_keywords = new Set(["$$props", "$$restProps", "$$slots"]);
 
 export function is_reserved_keyword(name) {
 	return reserved_keywords.has(name);
diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts
index 3fd0a2b70166..633b6f054cdf 100644
--- a/src/runtime/internal/utils.ts
+++ b/src/runtime/internal/utils.ts
@@ -128,6 +128,14 @@ export function compute_rest_props(props, keys) {
 	return rest;
 }
 
+export function compute_slots(slots) {
+	const result = {};
+	for (const key in slots) {
+		result[key] = true;
+	}
+	return result;
+}
+
 export function once(fn) {
 	let ran = false;
 	return function(this: any, ...args) {
diff --git a/test/js/samples/capture-inject-state/expected.js b/test/js/samples/capture-inject-state/expected.js
index 6aa93b9c5aef..6a0351ae449e 100644
--- a/test/js/samples/capture-inject-state/expected.js
+++ b/test/js/samples/capture-inject-state/expected.js
@@ -103,6 +103,8 @@ function instance($$self, $$props, $$invalidate) {
 		$$subscribe_prop = () => ($$unsubscribe_prop(), $$unsubscribe_prop = subscribe(prop, $$value => $$invalidate(2, $prop = $$value)), prop);
 
 	$$self.$$.on_destroy.push(() => $$unsubscribe_prop());
+	let { $$slots: slots = {}, $$scope } = $$props;
+	validate_slots("Component", slots, []);
 	let { prop } = $$props;
 	validate_store(prop, "prop");
 	$$subscribe_prop();
@@ -115,9 +117,6 @@ function instance($$self, $$props, $$invalidate) {
 		if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
 	});
 
-	let { $$slots = {}, $$scope } = $$props;
-	validate_slots("Component", $$slots, []);
-
 	$$self.$$set = $$props => {
 		if ("prop" in $$props) $$subscribe_prop($$invalidate(0, prop = $$props.prop));
 		if ("alias" in $$props) $$invalidate(1, realName = $$props.alias);
diff --git a/test/js/samples/debug-empty/expected.js b/test/js/samples/debug-empty/expected.js
index 6781e5333cd0..f427f1bf4581 100644
--- a/test/js/samples/debug-empty/expected.js
+++ b/test/js/samples/debug-empty/expected.js
@@ -69,6 +69,8 @@ function create_fragment(ctx) {
 }
 
 function instance($$self, $$props, $$invalidate) {
+	let { $$slots: slots = {}, $$scope } = $$props;
+	validate_slots("Component", slots, []);
 	let { name } = $$props;
 	const writable_props = ["name"];
 
@@ -76,9 +78,6 @@ function instance($$self, $$props, $$invalidate) {
 		if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
 	});
 
-	let { $$slots = {}, $$scope } = $$props;
-	validate_slots("Component", $$slots, []);
-
 	$$self.$$set = $$props => {
 		if ("name" in $$props) $$invalidate(0, name = $$props.name);
 	};
diff --git a/test/js/samples/debug-foo-bar-baz-things/expected.js b/test/js/samples/debug-foo-bar-baz-things/expected.js
index 087d2e399d3f..7439b3310b3a 100644
--- a/test/js/samples/debug-foo-bar-baz-things/expected.js
+++ b/test/js/samples/debug-foo-bar-baz-things/expected.js
@@ -170,6 +170,8 @@ function create_fragment(ctx) {
 }
 
 function instance($$self, $$props, $$invalidate) {
+	let { $$slots: slots = {}, $$scope } = $$props;
+	validate_slots("Component", slots, []);
 	let { things } = $$props;
 	let { foo } = $$props;
 	let { bar } = $$props;
@@ -180,9 +182,6 @@ function instance($$self, $$props, $$invalidate) {
 		if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
 	});
 
-	let { $$slots = {}, $$scope } = $$props;
-	validate_slots("Component", $$slots, []);
-
 	$$self.$$set = $$props => {
 		if ("things" in $$props) $$invalidate(0, things = $$props.things);
 		if ("foo" in $$props) $$invalidate(1, foo = $$props.foo);
diff --git a/test/js/samples/debug-foo/expected.js b/test/js/samples/debug-foo/expected.js
index 9f12bfb807a7..d869a5cf9e84 100644
--- a/test/js/samples/debug-foo/expected.js
+++ b/test/js/samples/debug-foo/expected.js
@@ -164,6 +164,8 @@ function create_fragment(ctx) {
 }
 
 function instance($$self, $$props, $$invalidate) {
+	let { $$slots: slots = {}, $$scope } = $$props;
+	validate_slots("Component", slots, []);
 	let { things } = $$props;
 	let { foo } = $$props;
 	const writable_props = ["things", "foo"];
@@ -172,9 +174,6 @@ function instance($$self, $$props, $$invalidate) {
 		if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
 	});
 
-	let { $$slots = {}, $$scope } = $$props;
-	validate_slots("Component", $$slots, []);
-
 	$$self.$$set = $$props => {
 		if ("things" in $$props) $$invalidate(0, things = $$props.things);
 		if ("foo" in $$props) $$invalidate(1, foo = $$props.foo);
diff --git a/test/js/samples/debug-hoisted/expected.js b/test/js/samples/debug-hoisted/expected.js
index 0e634297f08f..c6257ac90d5a 100644
--- a/test/js/samples/debug-hoisted/expected.js
+++ b/test/js/samples/debug-hoisted/expected.js
@@ -49,6 +49,8 @@ function create_fragment(ctx) {
 }
 
 function instance($$self, $$props, $$invalidate) {
+	let { $$slots: slots = {}, $$scope } = $$props;
+	validate_slots("Component", slots, []);
 	let obj = { x: 5 };
 	let kobzol = 5;
 	const writable_props = [];
@@ -57,8 +59,6 @@ function instance($$self, $$props, $$invalidate) {
 		if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
 	});
 
-	let { $$slots = {}, $$scope } = $$props;
-	validate_slots("Component", $$slots, []);
 	$$self.$capture_state = () => ({ obj, kobzol });
 
 	$$self.$inject_state = $$props => {
diff --git a/test/js/samples/debug-no-dependencies/expected.js b/test/js/samples/debug-no-dependencies/expected.js
index 76068e8cf464..4d8d05d3aa6e 100644
--- a/test/js/samples/debug-no-dependencies/expected.js
+++ b/test/js/samples/debug-no-dependencies/expected.js
@@ -136,14 +136,14 @@ function create_fragment(ctx) {
 }
 
 function instance($$self, $$props) {
+	let { $$slots: slots = {}, $$scope } = $$props;
+	validate_slots("Component", slots, []);
 	const writable_props = [];
 
 	Object.keys($$props).forEach(key => {
 		if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
 	});
 
-	let { $$slots = {}, $$scope } = $$props;
-	validate_slots("Component", $$slots, []);
 	return [];
 }
 
diff --git a/test/js/samples/debug-ssr-foo/expected.js b/test/js/samples/debug-ssr-foo/expected.js
index fe34a6a4fd42..69da37b2d921 100644
--- a/test/js/samples/debug-ssr-foo/expected.js
+++ b/test/js/samples/debug-ssr-foo/expected.js
@@ -1,7 +1,7 @@
 /* generated by Svelte vX.Y.Z */
 import { create_ssr_component, debug, each, escape } from "svelte/internal";
 
-const Component = create_ssr_component(($$result, $$props, $$bindings, $$slots) => {
+const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => {
 	let { things } = $$props;
 	let { foo } = $$props;
 	if ($$props.things === void 0 && $$bindings.things && things !== void 0) $$bindings.things(things);
diff --git a/test/js/samples/dev-warning-missing-data-computed/expected.js b/test/js/samples/dev-warning-missing-data-computed/expected.js
index fd34778f8d25..8c7f8bb1cff8 100644
--- a/test/js/samples/dev-warning-missing-data-computed/expected.js
+++ b/test/js/samples/dev-warning-missing-data-computed/expected.js
@@ -65,6 +65,8 @@ function create_fragment(ctx) {
 }
 
 function instance($$self, $$props, $$invalidate) {
+	let { $$slots: slots = {}, $$scope } = $$props;
+	validate_slots("Component", slots, []);
 	let { foo } = $$props;
 	let bar;
 	const writable_props = ["foo"];
@@ -73,9 +75,6 @@ function instance($$self, $$props, $$invalidate) {
 		if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
 	});
 
-	let { $$slots = {}, $$scope } = $$props;
-	validate_slots("Component", $$slots, []);
-
 	$$self.$$set = $$props => {
 		if ("foo" in $$props) $$invalidate(0, foo = $$props.foo);
 	};
diff --git a/test/js/samples/loop-protect/expected.js b/test/js/samples/loop-protect/expected.js
index 2ee7d90f17d1..1042b20823b8 100644
--- a/test/js/samples/loop-protect/expected.js
+++ b/test/js/samples/loop-protect/expected.js
@@ -67,6 +67,8 @@ function foo() {
 }
 
 function instance($$self, $$props, $$invalidate) {
+	let { $$slots: slots = {}, $$scope } = $$props;
+	validate_slots("Component", slots, []);
 	let node;
 
 	{
@@ -111,9 +113,6 @@ function instance($$self, $$props, $$invalidate) {
 		if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console_1.warn(`<Component> was created with unknown prop '${key}'`);
 	});
 
-	let { $$slots = {}, $$scope } = $$props;
-	validate_slots("Component", $$slots, []);
-
 	function div_binding($$value) {
 		binding_callbacks[$$value ? "unshift" : "push"](() => {
 			node = $$value;
diff --git a/test/js/samples/ssr-no-oncreate-etc/expected.js b/test/js/samples/ssr-no-oncreate-etc/expected.js
index 803f06a8829d..b91deb3e9732 100644
--- a/test/js/samples/ssr-no-oncreate-etc/expected.js
+++ b/test/js/samples/ssr-no-oncreate-etc/expected.js
@@ -15,7 +15,7 @@ function swipe(node, callback) {
 	
 } // TODO implement
 
-const Component = create_ssr_component(($$result, $$props, $$bindings, $$slots) => {
+const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => {
 	onMount(() => {
 		console.log("onMount");
 	});
diff --git a/test/js/samples/ssr-preserve-comments/expected.js b/test/js/samples/ssr-preserve-comments/expected.js
index 1dc12710c0bb..de9fa7582ed9 100644
--- a/test/js/samples/ssr-preserve-comments/expected.js
+++ b/test/js/samples/ssr-preserve-comments/expected.js
@@ -1,7 +1,7 @@
 /* generated by Svelte vX.Y.Z */
 import { create_ssr_component } from "svelte/internal";
 
-const Component = create_ssr_component(($$result, $$props, $$bindings, $$slots) => {
+const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => {
 	return `<div>content</div>
 
 <div>more content</div>`;
diff --git a/test/runtime/samples/$$slot/A.svelte b/test/runtime/samples/$$slot/A.svelte
new file mode 100644
index 000000000000..ffa166166ca9
--- /dev/null
+++ b/test/runtime/samples/$$slot/A.svelte
@@ -0,0 +1,31 @@
+<script>
+	let data = '';
+
+	if ($$slots.b) {
+		data = 'foo';
+	}
+
+	export function getData() {
+		return data;
+	}
+
+	function toString(data) {
+		const result = {};
+		const sortedKeys = Object.keys(data).sort();
+		sortedKeys.forEach(key => result[key] = data[key]);
+		return JSON.stringify(result);
+	}
+</script>
+
+<slot></slot>
+<slot name="a"></slot>
+
+$$slots: {toString($$slots)}
+
+{#if $$slots.b}
+	<div>
+		<slot name="b"></slot>
+	</div>
+{:else}
+	Slot b is not available
+{/if}
\ No newline at end of file
diff --git a/test/runtime/samples/$$slot/_config.js b/test/runtime/samples/$$slot/_config.js
new file mode 100644
index 000000000000..13b2137cdbc5
--- /dev/null
+++ b/test/runtime/samples/$$slot/_config.js
@@ -0,0 +1,18 @@
+export default {
+	html: `
+		<span>bye</span><span>world</span>
+		<span slot="a">hello world</span>
+		$$slots: {"a":true,"default":true}
+		Slot b is not available
+
+		<span>bye world</span>
+		<span slot="a">hello world</span>
+		$$slots: {"a":true,"b":true,"default":true}
+		<div><span slot="b">hello world</span></div>
+	`,
+
+	async test({ assert, target, component }) {
+		assert.equal(component.getA(), '');
+		assert.equal(component.getB(), 'foo');
+	}
+};
diff --git a/test/runtime/samples/$$slot/main.svelte b/test/runtime/samples/$$slot/main.svelte
new file mode 100644
index 000000000000..8b7efae5735a
--- /dev/null
+++ b/test/runtime/samples/$$slot/main.svelte
@@ -0,0 +1,23 @@
+<script>
+	import A from "./A.svelte";
+	let a, b;
+
+	export function getA() {
+		return a.getData();
+	}
+	export function getB() {
+		return b.getData();
+	}
+</script>
+
+<A bind:this={a}>
+  <span slot="a">hello world</span>
+  <span>bye</span>
+  <span>world</span>
+</A>
+
+<A bind:this={b}>
+  <span slot="a">hello world</span>
+  <span slot="b">hello world</span>
+  <span>bye world</span>
+</A>