diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index b1f516dad69d9..65e43c1278944 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -21337,11 +21337,19 @@ namespace ts {
             return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex, contextFlags);
         }
 
-        function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number, contextFlags?: ContextFlags): Type {
+        function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number, contextFlags?: ContextFlags): Type | undefined {
             // If we're already in the process of resolving the given signature, don't resolve again as
             // that could cause infinite recursion. Instead, return anySignature.
             let signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget);
-            if (contextFlags && contextFlags & ContextFlags.BaseConstraint && signature.target && !hasTypeArguments(callTarget)) {
+
+            if (contextFlags && contextFlags & ContextFlags.Uninstantiated) {
+                return signature.target ? getTypeAtPosition(signature.target, argIndex) : undefined;
+            }
+
+            if (contextFlags && contextFlags & ContextFlags.BaseConstraint) {
+                if (!signature.target || hasTypeArguments(callTarget)) {
+                    return undefined;
+                }
                 signature = getBaseSignature(signature.target);
             }
 
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index 98f2531b6c6d1..17f080385b58b 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -3605,6 +3605,7 @@ namespace ts {
         Signature      = 1 << 0, // Obtaining contextual signature
         NoConstraints  = 1 << 1, // Don't obtain type variable constraints
         BaseConstraint = 1 << 2, // Use base constraint type for completions
+        Uninstantiated = 1 << 3, // Attempt to get the type from an uninstantiated signature
     }
 
     // NOTE: If modifying this enum, must modify `TypeFormatFlags` too!
diff --git a/src/services/completions.ts b/src/services/completions.ts
index 471d8e4c142c6..ef445a5a79d24 100644
--- a/src/services/completions.ts
+++ b/src/services/completions.ts
@@ -1279,7 +1279,14 @@ namespace ts.Completions {
             // Cursor is inside a JSX self-closing element or opening element
             const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes);
             if (!attrsType) return GlobalsSearch.Continue;
-            const baseType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes, ContextFlags.BaseConstraint);
+            const uninstantiatedType = typeChecker.getContextualType(jsxContainer!.attributes, ContextFlags.Uninstantiated);
+            let baseType;
+            if (uninstantiatedType) {
+                const signature = tryGetContextualTypeProvidingSignature(jsxContainer!, typeChecker)?.target;
+                if (signature && !isIndexedAccessTypeWithTypeParameterIndex(uninstantiatedType, signature)) {
+                    baseType = typeChecker.getContextualType(jsxContainer!.attributes, ContextFlags.BaseConstraint);
+                }
+            }
             symbols = filterJsxAttributes(getPropertiesForObjectExpression(attrsType, baseType, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties);
             setSortTextToOptionalMember();
             completionKind = CompletionKind.MemberLike;
@@ -1800,9 +1807,17 @@ namespace ts.Completions {
 
             if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) {
                 const instantiatedType = typeChecker.getContextualType(objectLikeContainer);
-                const baseType = instantiatedType && typeChecker.getContextualType(objectLikeContainer, ContextFlags.BaseConstraint);
-                if (!instantiatedType || !baseType) return GlobalsSearch.Fail;
-                isNewIdentifierLocation = hasIndexSignature(instantiatedType || baseType);
+                if (!instantiatedType) return GlobalsSearch.Fail;
+                const uninstantiatedType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Uninstantiated);
+                let baseType;
+                if (uninstantiatedType) {
+                    const signature = tryGetContextualTypeProvidingSignature(objectLikeContainer, typeChecker)?.target;
+                    if (signature && !isIndexedAccessTypeWithTypeParameterIndex(uninstantiatedType, signature)) {
+                        baseType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.BaseConstraint);
+                    }
+                }
+
+                isNewIdentifierLocation = hasIndexSignature(instantiatedType);
                 typeMembers = getPropertiesForObjectExpression(instantiatedType, baseType, objectLikeContainer, typeChecker);
                 existingMembers = objectLikeContainer.properties;
             }
@@ -1852,6 +1867,68 @@ namespace ts.Completions {
             return GlobalsSearch.Success;
         }
 
+        function tryGetContextualTypeProvidingSignature(node: Node, checker: TypeChecker): Signature | undefined {
+            loop: while (true) {
+                switch (node.kind) {
+                    case SyntaxKind.SpreadAssignment:
+                    case SyntaxKind.ArrayLiteralExpression:
+                    case SyntaxKind.ParenthesizedExpression:
+                    case SyntaxKind.ConditionalExpression:
+                    case SyntaxKind.PropertyAssignment:
+                    case SyntaxKind.ShorthandPropertyAssignment:
+                    case SyntaxKind.ObjectLiteralExpression:
+                    case SyntaxKind.JsxAttribute:
+                    case SyntaxKind.JsxAttributes:
+                        node = node.parent;
+                        break;
+                    default:
+                        break loop;
+                }
+            }
+            if (!isCallLikeExpression(node)) {
+                return;
+            }
+            return checker.getResolvedSignature(node);
+        }
+
+        function isIndexedAccessTypeWithTypeParameterIndex(type: Type, signature: Signature): boolean {
+            if (type.isUnionOrIntersection()) {
+                return some(type.types, t => isIndexedAccessTypeWithTypeParameterIndex(t, signature));
+            }
+            if (type.flags & TypeFlags.IndexedAccess) {
+                return typeIsTypeParameterFromSignature((type as IndexedAccessType).indexType, signature);
+            }
+            if (getObjectFlags(type) & ObjectFlags.Mapped) {
+                const { constraintType } = (type as MappedType);
+                if (constraintType && constraintType.flags & TypeFlags.Index) {
+                    return isIndexedAccessTypeWithTypeParameterIndex((constraintType as IndexType).type, signature);
+                }
+            }
+            return false;
+        }
+
+        function typeIsTypeParameterFromSignature(type: Type, signature: Signature): boolean {
+            if (!signature.typeParameters) {
+                return false;
+            }
+            if (type.isUnionOrIntersection()) {
+                return some(type.types, t => typeIsTypeParameterFromSignature(t, signature));
+            }
+            if (type.flags & TypeFlags.Conditional) {
+                return typeIsTypeParameterFromSignature((type as ConditionalType).checkType, signature)
+                    || typeIsTypeParameterFromSignature((type as ConditionalType).extendsType, signature)
+                    || typeIsTypeParameterFromSignature((type as ConditionalType).resolvedTrueType, signature)
+                    || typeIsTypeParameterFromSignature((type as ConditionalType).resolvedFalseType, signature);
+            }
+            if (type.flags & TypeFlags.Index) {
+                return typeIsTypeParameterFromSignature((type as IndexType).type, signature);
+            }
+            if (type.flags & TypeFlags.TypeParameter) {
+                return some(signature.typeParameters, p => p.symbol === type.symbol);
+            }
+            return false;
+        }
+
         /**
          * Aggregates relevant symbols for completion in import clauses and export clauses
          * whose declarations have a module specifier; for instance, symbols will be aggregated for
diff --git a/tests/cases/fourslash/completionsGenericIndexedAccess3.ts b/tests/cases/fourslash/completionsGenericIndexedAccess3.ts
new file mode 100644
index 0000000000000..730cdd8f631dd
--- /dev/null
+++ b/tests/cases/fourslash/completionsGenericIndexedAccess3.ts
@@ -0,0 +1,35 @@
+/// <reference path="fourslash.ts" />
+
+////interface CustomElements {
+////  'component-one': {
+////      foo?: string;
+////  },
+////  'component-two': {
+////      bar?: string;
+////  }
+////}
+////
+////interface Options<T extends keyof CustomElements> {
+////  props: CustomElements[T];
+////}
+////
+////declare function create<T extends keyof CustomElements>(name: T, options: Options<T>): void;
+////
+////create('component-one', { props: { /*1*/ } });
+////create('component-two', { props: { /*2*/ } });
+
+verify.completions({
+  marker: "1",
+  exact: [{
+    name: "foo",
+    sortText: completion.SortText.OptionalMember
+  }]
+});
+
+verify.completions({
+  marker: "2",
+  exact: [{
+    name: "bar",
+    sortText: completion.SortText.OptionalMember
+  }]
+});
diff --git a/tests/cases/fourslash/completionsGenericIndexedAccess4.ts b/tests/cases/fourslash/completionsGenericIndexedAccess4.ts
new file mode 100644
index 0000000000000..0edeaedf9821f
--- /dev/null
+++ b/tests/cases/fourslash/completionsGenericIndexedAccess4.ts
@@ -0,0 +1,45 @@
+/// <reference path="fourslash.ts" />
+
+////interface CustomElements {
+////  'component-one': {
+////      foo?: string;
+////  },
+////  'component-two': {
+////      bar?: string;
+////  }
+////}
+////
+////interface Options<T extends keyof CustomElements> {
+////  props: CustomElements[T];
+////}
+////
+////declare function create<T extends 'hello' | 'goodbye'>(name: T, options: Options<T extends 'hello' ? 'component-one' : 'component-two'>): void;
+////declare function create<T extends keyof CustomElements>(name: T, options: Options<T>): void;
+////
+////create('hello', { props: { /*1*/ } })
+////create('goodbye', { props: { /*2*/ } })
+////create('component-one', { props: { /*3*/ } });
+
+verify.completions({
+  marker: "1",
+  exact: [{
+    name: "foo",
+    sortText: completion.SortText.OptionalMember
+  }]
+});
+
+verify.completions({
+  marker: "2",
+  exact: [{
+    name: "bar",
+    sortText: completion.SortText.OptionalMember
+  }]
+});
+
+verify.completions({
+  marker: "3",
+  exact: [{
+    name: "foo",
+    sortText: completion.SortText.OptionalMember
+  }]
+});
diff --git a/tests/cases/fourslash/completionsGenericIndexedAccess5.ts b/tests/cases/fourslash/completionsGenericIndexedAccess5.ts
new file mode 100644
index 0000000000000..7f3a0aa369094
--- /dev/null
+++ b/tests/cases/fourslash/completionsGenericIndexedAccess5.ts
@@ -0,0 +1,28 @@
+////interface CustomElements {
+////  'component-one': {
+////      foo?: string;
+////  },
+////  'component-two': {
+////      bar?: string;
+////  }
+////}
+////
+////interface Options<T extends keyof CustomElements> {
+////    props?: {} & { x: CustomElements[(T extends string ? T : never) & string][] }['x'];
+////}
+////
+////declare function f<T extends keyof CustomElements>(k: T, options: Options<T>): void;
+////
+////f("component-one", {
+////    props: [{
+////        /**/
+////    }]
+////})
+
+verify.completions({
+  marker: "",
+  exact: [{
+    name: "foo",
+    sortText: completion.SortText.OptionalMember
+  }]
+});
diff --git a/tests/cases/fourslash/completionsGenericIndexedAccess6.ts b/tests/cases/fourslash/completionsGenericIndexedAccess6.ts
new file mode 100644
index 0000000000000..496ebf7b50ecf
--- /dev/null
+++ b/tests/cases/fourslash/completionsGenericIndexedAccess6.ts
@@ -0,0 +1,23 @@
+// @Filename: component.tsx
+
+////interface CustomElements {
+////  'component-one': {
+////      foo?: string;
+////  },
+////  'component-two': {
+////      bar?: string;
+////  }
+////}
+////
+////type Options<T extends keyof CustomElements> = { kind: T } & Required<{ x: CustomElements[(T extends string ? T : never) & string] }['x']>;
+////
+////declare function Component<T extends keyof CustomElements>(props: Options<T>): void;
+////
+////const c = <Component /**/ kind="component-one" />
+
+verify.completions({
+  marker: "",
+  exact: [{
+    name: "foo"
+  }]
+})